push fix project
This commit is contained in:
parent
85181d866b
commit
9d516a5edf
|
|
@ -0,0 +1,63 @@
|
|||
<?php
|
||||
|
||||
namespace App\Console\Commands;
|
||||
|
||||
use App\Models\PenerimaFinal;
|
||||
use App\Models\PenerimaFinalArsip;
|
||||
use App\Models\CalonPenerima;
|
||||
use Illuminate\Console\Command;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
use Illuminate\Support\Facades\Schedule;
|
||||
|
||||
class ResetBulananPenerimaFinal extends Command
|
||||
{
|
||||
protected $signature = 'blt:reset-bulanan';
|
||||
protected $description = 'Arsipkan data penerima final lalu reset halaman admin dan RT';
|
||||
|
||||
public function handle(): int
|
||||
{
|
||||
$periode = now()->subMonth()->format('Y-m');
|
||||
$penerimas = PenerimaFinal::where('status_verifikasi', 'disetujui')->get();
|
||||
|
||||
if ($penerimas->isEmpty()) {
|
||||
$this->info('Tidak ada data untuk diarsipkan.');
|
||||
return 0;
|
||||
}
|
||||
|
||||
// LANGKAH A — Arsipkan semua data dulu
|
||||
foreach ($penerimas as $p) {
|
||||
PenerimaFinalArsip::create([
|
||||
'periode_arsip' => $periode,
|
||||
'penerima_final_id' => $p->id,
|
||||
'nama_lengkap' => $p->nama_lengkap,
|
||||
'nik' => $p->nik,
|
||||
'no_kk' => $p->no_kk,
|
||||
'nomor_rt' => $p->nomor_rt,
|
||||
'nama_dusun' => $p->nama_dusun,
|
||||
'pekerjaan' => $p->pekerjaan,
|
||||
'penghasilan' => $p->penghasilan,
|
||||
'jumlah_tanggungan' => $p->jumlah_tanggungan,
|
||||
'aset_kepemilikan' => $p->aset_kepemilikan,
|
||||
'bantuan_lain' => $p->bantuan_lain,
|
||||
'usia' => $p->usia,
|
||||
'probability' => $p->probability,
|
||||
'status_verifikasi' => $p->status_verifikasi,
|
||||
'tanggal_penetapan' => $p->tanggal_penetapan,
|
||||
'periode_bantuan' => $p->periode_bantuan,
|
||||
'jumlah_bantuan' => $p->jumlah_bantuan,
|
||||
]);
|
||||
}
|
||||
|
||||
// LANGKAH B — Kosongkan halaman admin
|
||||
PenerimaFinal::where('status_verifikasi', 'disetujui')->delete();
|
||||
|
||||
// LANGKAH C — Kosongkan halaman RT
|
||||
CalonPenerima::where('tracking_status', 'selesai')
|
||||
->update(['tracking_status' => 'belum_validasi']);
|
||||
|
||||
Log::info("Reset bulanan selesai: {$penerimas->count()} data diarsipkan periode {$periode}.");
|
||||
$this->info("Selesai! {$penerimas->count()} data diarsipkan ({$periode}). Admin & RT sudah kosong.");
|
||||
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,60 @@
|
|||
<?php
|
||||
|
||||
namespace App\Console\Commands;
|
||||
|
||||
use App\Models\PenerimaFinal;
|
||||
use App\Models\RiwayatPenerima;
|
||||
use Illuminate\Console\Command;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
|
||||
class ResetPenerimaFinalBulanan extends Command
|
||||
{
|
||||
protected $signature = 'penerima:reset-bulanan';
|
||||
protected $description = 'Arsipkan semua data penerima_finals ke riwayat_penerima lalu kosongkan tabelnya (dijalankan tiap tanggal 1)';
|
||||
|
||||
public function handle(): int
|
||||
{
|
||||
$periode = now()->subMonth()->format('Y-m'); // arsip bulan sebelumnya
|
||||
|
||||
$semua = PenerimaFinal::all();
|
||||
|
||||
if ($semua->isEmpty()) {
|
||||
$this->info('Tidak ada data penerima_finals untuk diarsipkan.');
|
||||
return self::SUCCESS;
|
||||
}
|
||||
|
||||
DB::transaction(function () use ($semua, $periode) {
|
||||
foreach ($semua as $p) {
|
||||
RiwayatPenerima::create([
|
||||
'periode' => $periode,
|
||||
'nama_lengkap' => $p->nama_lengkap,
|
||||
'nik' => $p->nik,
|
||||
'no_kk' => $p->no_kk,
|
||||
'rt_id' => $p->rt_id,
|
||||
'nomor_rt' => $p->nomor_rt,
|
||||
'nama_dusun' => $p->nama_dusun,
|
||||
'pekerjaan' => $p->pekerjaan,
|
||||
'penghasilan' => $p->penghasilan,
|
||||
'jumlah_tanggungan' => $p->jumlah_tanggungan,
|
||||
'aset_kepemilikan' => $p->aset_kepemilikan,
|
||||
'kondisi_rumah' => $p->kondisi_rumah,
|
||||
'meteran_listrik' => $p->meteran_listrik,
|
||||
'sumber_air' => $p->sumber_air,
|
||||
'bantuan_lain' => $p->bantuan_lain,
|
||||
'usia' => $p->usia,
|
||||
'probability' => $p->probability,
|
||||
'status_verifikasi' => $p->status_verifikasi,
|
||||
'tanggal_penetapan' => $p->tanggal_penetapan,
|
||||
'jumlah_bantuan' => $p->jumlah_bantuan,
|
||||
'di_arsipkan_pada' => now(),
|
||||
]);
|
||||
}
|
||||
|
||||
// Kosongkan tabel penerima_finals
|
||||
PenerimaFinal::truncate();
|
||||
});
|
||||
|
||||
$this->info("Berhasil mengarsipkan {$semua->count()} data penerima ke periode {$periode}.");
|
||||
return self::SUCCESS;
|
||||
}
|
||||
}
|
||||
|
|
@ -6,6 +6,7 @@
|
|||
use App\Models\CalonPenerima;
|
||||
use App\Models\Dusun;
|
||||
use App\Models\PenerimaFinal;
|
||||
use App\Models\PeriodeDanaBlt;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
|
||||
|
|
@ -37,6 +38,7 @@ public function index(Request $request)
|
|||
$query = CalonPenerima::query()
|
||||
->with(['rt.dusun', 'prediksiKelayakan'])
|
||||
->whereIn('tracking_status', ['sedang_validasi', 'selesai'])
|
||||
->where('status_verifikasi', '!=', 'ditolak')
|
||||
->whereHas('rt', function ($q) use ($dusunId) {
|
||||
$q->where('dusun_id', $dusunId);
|
||||
});
|
||||
|
|
@ -54,7 +56,59 @@ public function index(Request $request)
|
|||
->pluck('nik');
|
||||
}
|
||||
|
||||
return view('admin.filterisasi', compact('dusuns', 'dusunId', 'kuota', 'candidates', 'pickedNiks'));
|
||||
// Ambil riwayat periode dana
|
||||
$periodeDana = PeriodeDanaBlt::orderBy('id')->get();
|
||||
$periodeTerakhir = $periodeDana->last();
|
||||
$nextPeriodeNum = $periodeDana->count() + 1;
|
||||
$danaSisaTerakhir = $periodeTerakhir ? $periodeTerakhir->dana_sisa : null;
|
||||
|
||||
return view('admin.filterisasi', compact(
|
||||
'dusuns', 'dusunId', 'kuota', 'candidates', 'pickedNiks',
|
||||
'periodeDana', 'periodeTerakhir', 'nextPeriodeNum', 'danaSisaTerakhir'
|
||||
));
|
||||
}
|
||||
|
||||
public function simpanPeriode(Request $request)
|
||||
{
|
||||
$user = $request->user();
|
||||
|
||||
if (!$user || $user->role !== 'admin') {
|
||||
abort(403, 'Akses ditolak: hanya Admin.');
|
||||
}
|
||||
|
||||
$data = $request->validate([
|
||||
'periode' => 'required|string|max:50',
|
||||
'dana_awal' => 'required|integer|min:1',
|
||||
'jumlah_penerima' => 'required|integer|min:1',
|
||||
'keterangan' => 'nullable|string|max:255',
|
||||
]);
|
||||
|
||||
$danaTerpakai = $data['jumlah_penerima'] * 300000;
|
||||
$danaSisa = $data['dana_awal'] - $danaTerpakai;
|
||||
|
||||
PeriodeDanaBlt::create([
|
||||
'periode' => $data['periode'],
|
||||
'dana_awal' => $data['dana_awal'],
|
||||
'jumlah_penerima' => $data['jumlah_penerima'],
|
||||
'dana_terpakai' => $danaTerpakai,
|
||||
'dana_sisa' => max(0, $danaSisa),
|
||||
'keterangan' => $data['keterangan'] ?? null,
|
||||
]);
|
||||
|
||||
return back()->with('success', "Periode {$data['periode']} berhasil disimpan. Sisa dana: Rp " . number_format(max(0, $danaSisa), 0, ',', '.'));
|
||||
}
|
||||
|
||||
public function hapusPeriode(Request $request, $id)
|
||||
{
|
||||
$user = $request->user();
|
||||
|
||||
if (!$user || $user->role !== 'admin') {
|
||||
abort(403, 'Akses ditolak: hanya Admin.');
|
||||
}
|
||||
|
||||
PeriodeDanaBlt::findOrFail($id)->delete();
|
||||
|
||||
return back()->with('success', 'Data periode berhasil dihapus.');
|
||||
}
|
||||
|
||||
public function tetapkan(Request $request)
|
||||
|
|
@ -79,6 +133,7 @@ public function tetapkan(Request $request)
|
|||
$allCandidates = CalonPenerima::query()
|
||||
->with(['rt.dusun', 'prediksiKelayakan'])
|
||||
->whereIn('tracking_status', ['sedang_validasi', 'selesai'])
|
||||
->where('status_verifikasi', '!=', 'ditolak')
|
||||
->whereHas('rt', function ($r) use ($dusunId) {
|
||||
$r->where('dusun_id', $dusunId);
|
||||
})
|
||||
|
|
@ -166,6 +221,7 @@ public function resetDusun(Request $request)
|
|||
$r->where('dusun_id', $dusun->id);
|
||||
})
|
||||
->whereIn('tracking_status', ['sedang_validasi', 'selesai'])
|
||||
->where('status_verifikasi', '!=', 'ditolak')
|
||||
->pluck('id')
|
||||
->all();
|
||||
|
||||
|
|
|
|||
|
|
@ -1,146 +1,220 @@
|
|||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Admin;
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Models\CalonPenerima;
|
||||
use App\Models\PenerimaFinal;
|
||||
use App\Models\LaporanPublik;
|
||||
use App\Models\PenerimaFinal;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Storage;
|
||||
use Barryvdh\DomPDF\Facade\Pdf;
|
||||
use App\Exports\PenerimaFinalExport;
|
||||
use Maatwebsite\Excel\Facades\Excel;
|
||||
|
||||
class LaporanController extends Controller
|
||||
class LandingPageController extends Controller
|
||||
{
|
||||
public function index(Request $request)
|
||||
{
|
||||
$user = $request->user();
|
||||
$nik = trim((string) $request->query('nik', ''));
|
||||
$statusData = null;
|
||||
$penerimaFinal = null;
|
||||
$explanation = ['positive' => [], 'negative' => [], 'summary' => null];
|
||||
|
||||
if (!$user || $user->role !== 'admin') {
|
||||
abort(403, 'Akses ditolak: hanya Admin.');
|
||||
if ($nik !== '') {
|
||||
$statusData = CalonPenerima::with(['rt.dusun', 'prediksiKelayakan'])
|
||||
->where('nik', $nik)
|
||||
->first();
|
||||
|
||||
if ($statusData) {
|
||||
// ── Cari PenerimaFinal secara manual ──
|
||||
// Coba berbagai kemungkinan foreign key / kolom pencarian
|
||||
try {
|
||||
$penerimaFinal = PenerimaFinal::where('nik', $nik)->first();
|
||||
} catch (\Exception $e) {
|
||||
// Kolom 'nik' tidak ada, coba cara lain
|
||||
}
|
||||
|
||||
if (!$penerimaFinal) {
|
||||
try {
|
||||
$penerimaFinal = PenerimaFinal::where('calon_id', $statusData->id)->first();
|
||||
} catch (\Exception $e) {}
|
||||
}
|
||||
|
||||
if (!$penerimaFinal) {
|
||||
try {
|
||||
$penerimaFinal = PenerimaFinal::where('id_calon', $statusData->id)->first();
|
||||
} catch (\Exception $e) {}
|
||||
}
|
||||
|
||||
if (!$penerimaFinal) {
|
||||
try {
|
||||
$penerimaFinal = PenerimaFinal::where('user_id', $statusData->user_id)->first();
|
||||
} catch (\Exception $e) {}
|
||||
}
|
||||
|
||||
// ── Parse explanation dari prediksiKelayakan ──
|
||||
if ($statusData->prediksiKelayakan) {
|
||||
$pred = $statusData->prediksiKelayakan;
|
||||
|
||||
$raw = $pred->explanation
|
||||
?? $pred->feature_explanation
|
||||
?? $pred->alasan
|
||||
?? null;
|
||||
|
||||
if (is_string($raw) && !empty($raw)) {
|
||||
$decoded = json_decode($raw, true);
|
||||
if (is_array($decoded)) {
|
||||
$explanation = [
|
||||
'positive' => $decoded['positive'] ?? $decoded['pendukung'] ?? [],
|
||||
'negative' => $decoded['negative'] ?? $decoded['pengurang'] ?? [],
|
||||
'summary' => $decoded['summary'] ?? $decoded['ringkasan'] ?? null,
|
||||
];
|
||||
}
|
||||
} elseif (is_array($raw)) {
|
||||
$explanation = [
|
||||
'positive' => $raw['positive'] ?? $raw['pendukung'] ?? [],
|
||||
'negative' => $raw['negative'] ?? $raw['pengurang'] ?? [],
|
||||
'summary' => $raw['summary'] ?? $raw['ringkasan'] ?? null,
|
||||
];
|
||||
}
|
||||
|
||||
// Fallback field lain dari prediksi
|
||||
if (empty($explanation['positive']) && empty($explanation['negative'])) {
|
||||
foreach (['feature_importances', 'features', 'detail', 'data'] as $field) {
|
||||
if (!empty($pred->$field)) {
|
||||
$val = is_string($pred->$field) ? json_decode($pred->$field, true) : $pred->$field;
|
||||
if (is_array($val) && isset($val['positive'])) {
|
||||
$explanation = [
|
||||
'positive' => $val['positive'] ?? [],
|
||||
'negative' => $val['negative'] ?? [],
|
||||
'summary' => $val['summary'] ?? null,
|
||||
];
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ── GENERATE OTOMATIS dari data CalonPenerima ──
|
||||
// Dijalankan jika explanation masih kosong setelah semua fallback
|
||||
if (empty($explanation['positive']) && empty($explanation['negative'])) {
|
||||
$cp = $statusData;
|
||||
$positive = [];
|
||||
$negative = [];
|
||||
|
||||
// Pekerjaan
|
||||
$pekerjaan = strtolower($cp->pekerjaan ?? '');
|
||||
if (in_array($pekerjaan, ['tidak bekerja', 'tidak ada', '-', ''])) {
|
||||
$positive[] = 'Tidak bekerja / tidak memiliki penghasilan tetap';
|
||||
} elseif (in_array($pekerjaan, ['petani', 'buruh', 'nelayan', 'pemulung', 'pedagang kecil'])) {
|
||||
$positive[] = 'Pekerjaan termasuk kategori rentan ('. ucfirst($pekerjaan) .')';
|
||||
} else {
|
||||
$negative[] = 'Memiliki pekerjaan (' . ucfirst($pekerjaan) . ')';
|
||||
}
|
||||
|
||||
// Penghasilan
|
||||
$penghasilan = (float)($cp->penghasilan ?? 0);
|
||||
if ($penghasilan == 0) {
|
||||
$positive[] = 'Penghasilan Rp 0 (tidak ada penghasilan)';
|
||||
} elseif ($penghasilan < 500000) {
|
||||
$positive[] = 'Penghasilan sangat rendah (Rp ' . number_format($penghasilan, 0, ',', '.') . '/bulan)';
|
||||
} elseif ($penghasilan < 1500000) {
|
||||
$positive[] = 'Penghasilan di bawah UMR (Rp ' . number_format($penghasilan, 0, ',', '.') . '/bulan)';
|
||||
} else {
|
||||
$negative[] = 'Penghasilan cukup (Rp ' . number_format($penghasilan, 0, ',', '.') . '/bulan)';
|
||||
}
|
||||
|
||||
// Tanggungan
|
||||
$tanggungan = (int)($cp->jumlah_tanggungan ?? 0);
|
||||
if ($tanggungan >= 4) {
|
||||
$positive[] = 'Jumlah tanggungan banyak (' . $tanggungan . ' orang)';
|
||||
} elseif ($tanggungan >= 2) {
|
||||
$positive[] = 'Memiliki tanggungan ' . $tanggungan . ' orang';
|
||||
} else {
|
||||
$negative[] = 'Tanggungan sedikit (' . $tanggungan . ' orang)';
|
||||
}
|
||||
|
||||
// Aset
|
||||
$aset = strtolower($cp->aset_kepemilikan ?? '');
|
||||
if (in_array($aset, ['tidak punya', 'tidak ada', 'tidak memiliki', '-', ''])) {
|
||||
$positive[] = 'Tidak memiliki aset berarti';
|
||||
} else {
|
||||
$negative[] = 'Memiliki aset kepemilikan (' . ucfirst($aset) . ')';
|
||||
}
|
||||
|
||||
// Bantuan lain
|
||||
$bantuan = strtolower($cp->bantuan_lain ?? '');
|
||||
if (in_array($bantuan, ['tidak', 'tidak ada', '-', ''])) {
|
||||
$positive[] = 'Belum menerima bantuan sosial lain';
|
||||
} else {
|
||||
$negative[] = 'Sudah menerima bantuan lain (' . $cp->bantuan_lain . ')';
|
||||
}
|
||||
|
||||
// Usia
|
||||
$usia = $cp->usia ?? ($cp->tanggal_lahir
|
||||
? \Carbon\Carbon::parse($cp->tanggal_lahir)->age
|
||||
: null);
|
||||
if ($usia !== null) {
|
||||
if ($usia >= 60) {
|
||||
$positive[] = 'Usia lanjut (' . $usia . ' tahun) — prioritas penerima';
|
||||
} elseif ($usia >= 50) {
|
||||
$positive[] = 'Usia ' . $usia . ' tahun (termasuk kelompok rentan)';
|
||||
} elseif ($usia < 18) {
|
||||
$positive[] = 'Usia ' . $usia . ' tahun (anak/remaja dalam keluarga)';
|
||||
}
|
||||
}
|
||||
|
||||
// Kondisi rumah
|
||||
$kondisi = strtolower($cp->kondisi_rumah ?? '');
|
||||
if (in_array($kondisi, ['tidak layak', 'buruk', 'rusak', 'sangat buruk'])) {
|
||||
$positive[] = 'Kondisi rumah tidak layak huni';
|
||||
} elseif (in_array($kondisi, ['sedang', 'cukup'])) {
|
||||
// netral, tidak tambah ke mana-mana
|
||||
} elseif (!empty($kondisi) && !in_array($kondisi, ['-', ''])) {
|
||||
$negative[] = 'Kondisi rumah ' . ucfirst($kondisi);
|
||||
}
|
||||
|
||||
// Meteran listrik
|
||||
$meteran = strtolower($cp->meteran_listrik ?? '');
|
||||
if (in_array($meteran, ['450va', '450 va'])) {
|
||||
$positive[] = 'Meteran listrik 450VA (golongan sangat miskin)';
|
||||
} elseif (in_array($meteran, ['900va', '900 va'])) {
|
||||
$positive[] = 'Meteran listrik 900VA (golongan miskin/subsidi)';
|
||||
} elseif (!empty($meteran) && !in_array($meteran, ['-', ''])) {
|
||||
$negative[] = 'Meteran listrik ' . strtoupper($cp->meteran_listrik);
|
||||
}
|
||||
|
||||
// Sumber air
|
||||
$air = strtolower($cp->sumber_air ?? '');
|
||||
if (in_array($air, ['sumur', 'sungai', 'mata air', 'tadah hujan'])) {
|
||||
$positive[] = 'Sumber air ' . ucfirst($air) . ' (bukan PDAM)';
|
||||
} elseif (!empty($air) && !in_array($air, ['-', ''])) {
|
||||
$negative[] = 'Sumber air ' . ucfirst($air);
|
||||
}
|
||||
|
||||
// Rekomendasi prediksi
|
||||
$rek = strtolower($pred->recommendation ?? $pred->status ?? '');
|
||||
$isLayakPred = in_array($rek, ['layak', 'ya', 'yes', '1', 'true']);
|
||||
|
||||
$explanation = [
|
||||
'positive' => $positive,
|
||||
'negative' => $negative,
|
||||
'summary' => 'Nilai kelayakan dihitung berdasarkan pekerjaan, penghasilan, tanggungan, aset, bantuan lain, usia, kondisi rumah, meteran listrik, dan sumber air.'
|
||||
. (count($positive) > 0 && count($negative) > 0
|
||||
? ' Terdapat faktor pendukung dan pengurang.'
|
||||
: (count($positive) > 0 ? ' Terdapat beberapa faktor pendukung.' : ' Tidak ada faktor yang signifikan.')),
|
||||
];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$laporans = PenerimaFinal::query()
|
||||
->where('status_verifikasi', 'disetujui')
|
||||
->orderByDesc('tanggal_penetapan')
|
||||
$laporanPubliks = LaporanPublik::where('is_active', true)
|
||||
->latest()
|
||||
->get();
|
||||
|
||||
$laporanPubliks = LaporanPublik::latest()->get();
|
||||
|
||||
return view('admin.laporan', compact('laporans', 'laporanPubliks'));
|
||||
}
|
||||
|
||||
public function exportPdf(Request $request)
|
||||
{
|
||||
$user = $request->user();
|
||||
|
||||
if (!$user || $user->role !== 'admin') {
|
||||
abort(403, 'Akses ditolak: hanya Admin.');
|
||||
}
|
||||
|
||||
$laporans = PenerimaFinal::query()
|
||||
->where('status_verifikasi', 'disetujui')
|
||||
->orderByDesc('tanggal_penetapan')
|
||||
->get();
|
||||
|
||||
$pdf = Pdf::loadView('admin.laporan-pdf', compact('laporans'))
|
||||
->setPaper('a4', 'landscape');
|
||||
|
||||
return $pdf->download('laporan-penerima-blt-dd-kelurahan-ngerong.pdf');
|
||||
}
|
||||
|
||||
public function exportExcel(Request $request)
|
||||
{
|
||||
$user = $request->user();
|
||||
|
||||
if (!$user || $user->role !== 'admin') {
|
||||
abort(403, 'Akses ditolak: hanya Admin.');
|
||||
}
|
||||
|
||||
return Excel::download(new PenerimaFinalExport, 'laporan-penerima-blt-dd-kelurahan-ngerong.xlsx');
|
||||
}
|
||||
|
||||
public function kirimKeRt(Request $request)
|
||||
{
|
||||
$user = $request->user();
|
||||
|
||||
if (!$user || $user->role !== 'admin') {
|
||||
abort(403, 'Akses ditolak: hanya Admin.');
|
||||
}
|
||||
|
||||
$candidateIds = CalonPenerima::query()
|
||||
->whereIn('tracking_status', ['sedang_validasi'])
|
||||
->whereIn('status_verifikasi', ['disetujui', 'ditolak'])
|
||||
->pluck('id')
|
||||
->all();
|
||||
|
||||
if (!empty($candidateIds)) {
|
||||
CalonPenerima::whereIn('id', $candidateIds)->update([
|
||||
'tracking_status' => 'selesai',
|
||||
]);
|
||||
}
|
||||
|
||||
return back()->with('success', 'Hasil final berhasil dikirim ke RT dusun. RT sekarang hanya dapat melihat status diterima atau ditolak.');
|
||||
}
|
||||
|
||||
public function uploadPublikPdf(Request $request)
|
||||
{
|
||||
$user = $request->user();
|
||||
|
||||
if (!$user || $user->role !== 'admin') {
|
||||
abort(403, 'Akses ditolak: hanya Admin.');
|
||||
}
|
||||
|
||||
$data = $request->validate([
|
||||
'judul' => 'required|string|max:255',
|
||||
'file_pdf' => 'required|mimes:pdf|max:5120',
|
||||
]);
|
||||
|
||||
$path = $request->file('file_pdf')->store('laporan-publik', 'public');
|
||||
|
||||
LaporanPublik::create([
|
||||
'judul' => $data['judul'],
|
||||
'file_path' => $path,
|
||||
'uploaded_by' => $user->id,
|
||||
'is_active' => true,
|
||||
]);
|
||||
|
||||
return back()->with('success', 'PDF laporan publik berhasil diupload.');
|
||||
}
|
||||
|
||||
public function hapusPublikPdf(Request $request, $id)
|
||||
{
|
||||
$user = $request->user();
|
||||
|
||||
if (!$user || $user->role !== 'admin') {
|
||||
abort(403, 'Akses ditolak: hanya Admin.');
|
||||
}
|
||||
|
||||
$laporan = LaporanPublik::findOrFail($id);
|
||||
|
||||
if ($laporan->file_path && Storage::disk('public')->exists($laporan->file_path)) {
|
||||
Storage::disk('public')->delete($laporan->file_path);
|
||||
}
|
||||
|
||||
$laporan->delete();
|
||||
|
||||
return back()->with('success', 'PDF laporan publik berhasil dihapus.');
|
||||
}
|
||||
|
||||
public function bersihkanRiwayatSelesai(Request $request)
|
||||
{
|
||||
$user = $request->user();
|
||||
|
||||
if (!$user || $user->role !== 'admin') {
|
||||
abort(403, 'Akses ditolak: hanya Admin.');
|
||||
}
|
||||
|
||||
$deleted = \App\Models\CalonPenerima::query()
|
||||
->where('tracking_status', 'selesai')
|
||||
->delete();
|
||||
|
||||
return back()->with('success', "Berhasil membersihkan {$deleted} data riwayat selesai. Arsip laporan tetap tersimpan.");
|
||||
return view('landing', compact(
|
||||
'nik',
|
||||
'statusData',
|
||||
'penerimaFinal',
|
||||
'laporanPubliks',
|
||||
'explanation'
|
||||
));
|
||||
}
|
||||
}
|
||||
|
|
@ -10,24 +10,38 @@ class LandingPageController extends Controller
|
|||
{
|
||||
public function index(Request $request)
|
||||
{
|
||||
$nik = trim((string) $request->query('nik',''));
|
||||
$nik = trim((string) $request->query('nik', ''));
|
||||
|
||||
$statusData = null;
|
||||
$penerimaFinal = null;
|
||||
|
||||
if($nik !== ''){
|
||||
if ($nik !== '') {
|
||||
$statusData = CalonPenerima::with(['rt.dusun'])
|
||||
->where('nik',$nik)
|
||||
->where('nik', $nik)
|
||||
->first();
|
||||
|
||||
// LOGIKA PENETAPAN: pakai kolom status_verifikasi yang benar-benar ada di tabel
|
||||
if ($statusData && isset($statusData->status_verifikasi)) {
|
||||
$currentStatus = strtolower($statusData->status_verifikasi);
|
||||
|
||||
if (in_array($currentStatus, ['ditolak', 'disetujui'])) {
|
||||
// Buat object penampung agar dibaca sebagai '$penerimaFinal' oleh file Blade Anda
|
||||
$penerimaFinal = new \stdClass();
|
||||
$penerimaFinal->status = $statusData->status_verifikasi;
|
||||
$penerimaFinal->created_at = $statusData->updated_at ?? now();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$laporanPubliks = LaporanPublik::where('is_active',true)
|
||||
$laporanPubliks = LaporanPublik::where('is_active', true)
|
||||
->latest()
|
||||
->get();
|
||||
|
||||
return view('landing',compact(
|
||||
return view('landing', compact(
|
||||
'nik',
|
||||
'statusData',
|
||||
'laporanPubliks'
|
||||
'laporanPubliks',
|
||||
'penerimaFinal'
|
||||
));
|
||||
}
|
||||
}
|
||||
|
|
@ -7,11 +7,13 @@
|
|||
use App\Models\PrediksiKelayakan;
|
||||
use App\Models\RT;
|
||||
use App\Services\MLPredictionService;
|
||||
use Carbon\Carbon;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Illuminate\Support\Facades\Storage;
|
||||
|
||||
|
||||
class CalonPenerimaController extends Controller
|
||||
{
|
||||
protected MLPredictionService $mlService;
|
||||
|
|
@ -58,35 +60,40 @@ public function store(Request $request)
|
|||
}
|
||||
|
||||
$validated = $request->validate([
|
||||
'rt_id' => 'nullable|exists:rts,id',
|
||||
'no_kk' => 'required|digits:16|unique:calon_penerimas,no_kk',
|
||||
'nik' => 'required|digits:16|unique:calon_penerimas,nik',
|
||||
'nama_lengkap' => 'required|string|max:255',
|
||||
'jenis_kelamin' => 'required|in:Laki-laki,Perempuan',
|
||||
'tempat_lahir' => 'required|string|max:255',
|
||||
'tanggal_lahir' => 'required|date',
|
||||
'alamat' => 'required|string',
|
||||
'desa' => 'nullable|string|max:255',
|
||||
'pekerjaan' => 'required|string',
|
||||
'penghasilan' => 'required|numeric|min:0',
|
||||
'jumlah_tanggungan' => 'required|integer|min:0',
|
||||
'aset_kepemilikan' => 'required|string',
|
||||
'bantuan_lain' => 'required|in:ya,tidak',
|
||||
'usia' => 'required|integer|min:17|max:100',
|
||||
'status_perkawinan' => 'required|string',
|
||||
'kondisi_rumah' => 'required|in:Layak,Sedang,Tidak Layak',
|
||||
'meteran_listrik' => 'required|in:450VA,900VA,1300VA+',
|
||||
'sumber_air' => 'required|in:PDAM,Sumur,Sungai',
|
||||
// Upload foto (opsional)
|
||||
'foto_rumah_depan' => 'nullable|image|max:2048',
|
||||
'foto_rumah_belakang' => 'nullable|image|max:2048',
|
||||
'foto_rumah_kanan' => 'nullable|image|max:2048',
|
||||
'foto_rumah_kiri' => 'nullable|image|max:2048',
|
||||
'foto_kk' => 'nullable|image|max:2048',
|
||||
'foto_ktp' => 'nullable|image|max:2048',
|
||||
'foto_rekening_listrik' => 'nullable|mimes:jpg,jpeg,png,pdf|max:2048',
|
||||
'foto_meteran_air' => 'nullable|image|max:2048',
|
||||
'dokumen_pendukung' => 'nullable|mimes:jpg,jpeg,png,pdf,doc,docx|max:5120',
|
||||
'rt_id' => 'nullable|exists:rts,id',
|
||||
'no_kk' => 'required|digits:16|unique:calon_penerimas,no_kk',
|
||||
'nik' => 'required|digits:16|unique:calon_penerimas,nik',
|
||||
'nama_lengkap' => 'required|string|max:255',
|
||||
'jenis_kelamin' => 'required|in:Laki-laki,Perempuan',
|
||||
'tempat_lahir' => 'required|string|max:255',
|
||||
'tanggal_lahir' => 'required|date',
|
||||
'alamat' => 'required|string',
|
||||
'desa' => 'nullable|string|max:255',
|
||||
'pekerjaan' => 'required|string',
|
||||
'penghasilan' => 'required|numeric|min:0',
|
||||
'jumlah_tanggungan' => 'required|integer|min:0',
|
||||
'aset_pilihan' => 'nullable|array',
|
||||
'aset_pilihan.*' => 'nullable|string',
|
||||
'aset_manual' => 'nullable|string|max:255',
|
||||
'bantuan_lain' => 'required|in:ya,tidak',
|
||||
'usia' => 'required|integer|min:17|max:100',
|
||||
'status_perkawinan' => 'required|string',
|
||||
'lantai_rumah' => 'required|string',
|
||||
'dinding_rumah' => 'required|string',
|
||||
'atap_rumah' => 'required|string',
|
||||
'luas_rumah_m2' => 'required|integer|min:1',
|
||||
'status_kepemilikan_rumah'=> 'required|string',
|
||||
'meteran_listrik' => 'required|in:450VA,900VA,1300VA+',
|
||||
'sumber_air' => 'required|in:PDAM,Sumur,Sungai',
|
||||
'foto_rumah_depan' => 'nullable|image',
|
||||
'foto_rumah_belakang' => 'nullable|image',
|
||||
'foto_rumah_kanan' => 'nullable|image',
|
||||
'foto_rumah_kiri' => 'nullable|image',
|
||||
'foto_kk' => 'nullable|image',
|
||||
'foto_ktp' => 'nullable|image',
|
||||
'foto_rekening_listrik' => 'nullable|mimes:jpg,jpeg,png,pdf',
|
||||
'foto_meteran_air' => 'nullable|image',
|
||||
'dokumen_pendukung' => 'nullable|mimes:jpg,jpeg,png,pdf,doc,docx',
|
||||
], [
|
||||
'nik.unique' => 'NIK sudah pernah diinput.',
|
||||
'no_kk.unique' => 'No. KK sudah pernah diinput.',
|
||||
|
|
@ -94,6 +101,17 @@ public function store(Request $request)
|
|||
|
||||
$validated['rt_id'] = $myRtId;
|
||||
|
||||
$asetGabung = $this->buildAsetString(
|
||||
$validated['aset_pilihan'] ?? [],
|
||||
$validated['aset_manual'] ?? ''
|
||||
);
|
||||
$validated['aset_kepemilikan'] = $asetGabung;
|
||||
|
||||
$validated['kondisi_rumah'] = $this->deriveKondisiRumah(
|
||||
$validated['lantai_rumah'],
|
||||
$validated['dinding_rumah']
|
||||
);
|
||||
|
||||
$rt = RT::with('dusun')->find($myRtId);
|
||||
if ($rt && $rt->dusun) {
|
||||
$validated['desa'] = $rt->dusun->nama_dusun;
|
||||
|
|
@ -103,62 +121,81 @@ public function store(Request $request)
|
|||
|
||||
DB::beginTransaction();
|
||||
try {
|
||||
// Handle upload foto
|
||||
$fotoFields = [
|
||||
$fotoImages = [
|
||||
'foto_rumah_depan', 'foto_rumah_belakang', 'foto_rumah_kanan', 'foto_rumah_kiri',
|
||||
'foto_kk', 'foto_ktp', 'foto_rekening_listrik', 'foto_meteran_air', 'dokumen_pendukung',
|
||||
'foto_kk', 'foto_ktp', 'foto_meteran_air',
|
||||
];
|
||||
foreach ($fotoFields as $field) {
|
||||
$fotoFiles = [
|
||||
'foto_rekening_listrik', 'dokumen_pendukung',
|
||||
];
|
||||
|
||||
foreach ($fotoImages as $field) {
|
||||
if ($request->hasFile($field)) {
|
||||
$validated[$field] = $this->simpanFotoKompres($request->file($field));
|
||||
}
|
||||
}
|
||||
foreach ($fotoFiles as $field) {
|
||||
if ($request->hasFile($field)) {
|
||||
$validated[$field] = $request->file($field)->store('dokumen-warga', 'public');
|
||||
}
|
||||
}
|
||||
|
||||
$calonPenerima = CalonPenerima::create([
|
||||
'user_id' => $user->id,
|
||||
'rt_id' => $validated['rt_id'],
|
||||
'no_kk' => $validated['no_kk'],
|
||||
'nik' => $validated['nik'],
|
||||
'nama_lengkap' => $validated['nama_lengkap'],
|
||||
'jenis_kelamin' => $validated['jenis_kelamin'],
|
||||
'tempat_lahir' => $validated['tempat_lahir'],
|
||||
'tanggal_lahir' => $validated['tanggal_lahir'],
|
||||
'alamat' => $validated['alamat'],
|
||||
'desa' => $validated['desa'],
|
||||
'pekerjaan' => $validated['pekerjaan'],
|
||||
'penghasilan' => $validated['penghasilan'],
|
||||
'jumlah_tanggungan' => $validated['jumlah_tanggungan'],
|
||||
'aset_kepemilikan' => $validated['aset_kepemilikan'],
|
||||
'kondisi_rumah' => $validated['kondisi_rumah'],
|
||||
'meteran_listrik' => $validated['meteran_listrik'],
|
||||
'sumber_air' => $validated['sumber_air'],
|
||||
'bantuan_lain' => $validated['bantuan_lain'],
|
||||
'usia' => $validated['usia'],
|
||||
'status_perkawinan' => $validated['status_perkawinan'],
|
||||
'status_verifikasi' => 'pending',
|
||||
'tracking_status' => 'draft',
|
||||
'foto_rumah_depan' => $validated['foto_rumah_depan'] ?? null,
|
||||
'foto_rumah_belakang' => $validated['foto_rumah_belakang'] ?? null,
|
||||
'foto_rumah_kanan' => $validated['foto_rumah_kanan'] ?? null,
|
||||
'foto_rumah_kiri' => $validated['foto_rumah_kiri'] ?? null,
|
||||
'foto_kk' => $validated['foto_kk'] ?? null,
|
||||
'foto_ktp' => $validated['foto_ktp'] ?? null,
|
||||
'foto_rekening_listrik' => $validated['foto_rekening_listrik'] ?? null,
|
||||
'foto_meteran_air' => $validated['foto_meteran_air'] ?? null,
|
||||
'dokumen_pendukung' => $validated['dokumen_pendukung'] ?? null,
|
||||
'user_id' => $user->id,
|
||||
'rt_id' => $validated['rt_id'],
|
||||
'no_kk' => $validated['no_kk'],
|
||||
'nik' => $validated['nik'],
|
||||
'nama_lengkap' => $validated['nama_lengkap'],
|
||||
'jenis_kelamin' => $validated['jenis_kelamin'],
|
||||
'tempat_lahir' => $validated['tempat_lahir'],
|
||||
'tanggal_lahir' => $validated['tanggal_lahir'],
|
||||
'alamat' => $validated['alamat'],
|
||||
'desa' => $validated['desa'],
|
||||
'pekerjaan' => $validated['pekerjaan'],
|
||||
'penghasilan' => $validated['penghasilan'],
|
||||
'jumlah_tanggungan' => $validated['jumlah_tanggungan'],
|
||||
'aset_kepemilikan' => $validated['aset_kepemilikan'],
|
||||
'kondisi_rumah' => $validated['kondisi_rumah'],
|
||||
'lantai_rumah' => $validated['lantai_rumah'],
|
||||
'dinding_rumah' => $validated['dinding_rumah'],
|
||||
'atap_rumah' => $validated['atap_rumah'],
|
||||
'luas_rumah_m2' => $validated['luas_rumah_m2'],
|
||||
'status_kepemilikan_rumah' => $validated['status_kepemilikan_rumah'],
|
||||
'meteran_listrik' => $validated['meteran_listrik'],
|
||||
'sumber_air' => $validated['sumber_air'],
|
||||
'bantuan_lain' => $validated['bantuan_lain'],
|
||||
'usia' => $validated['usia'],
|
||||
'status_perkawinan' => $validated['status_perkawinan'],
|
||||
'status_verifikasi' => 'pending',
|
||||
'tracking_status' => 'draft',
|
||||
'periode_bulan' => Carbon::now('Asia/Jakarta')->startOfMonth()->toDateString(),
|
||||
'foto_rumah_depan' => $validated['foto_rumah_depan'] ?? null,
|
||||
'foto_rumah_belakang' => $validated['foto_rumah_belakang'] ?? null,
|
||||
'foto_rumah_kanan' => $validated['foto_rumah_kanan'] ?? null,
|
||||
'foto_rumah_kiri' => $validated['foto_rumah_kiri'] ?? null,
|
||||
'foto_kk' => $validated['foto_kk'] ?? null,
|
||||
'foto_ktp' => $validated['foto_ktp'] ?? null,
|
||||
'foto_rekening_listrik' => $validated['foto_rekening_listrik'] ?? null,
|
||||
'foto_meteran_air' => $validated['foto_meteran_air'] ?? null,
|
||||
'dokumen_pendukung' => $validated['dokumen_pendukung'] ?? null,
|
||||
]);
|
||||
|
||||
// ML Prediction
|
||||
$predictionData = [
|
||||
'pekerjaan' => $validated['pekerjaan'],
|
||||
'penghasilan' => $validated['penghasilan'],
|
||||
'jumlah_tanggungan' => $validated['jumlah_tanggungan'],
|
||||
'aset_kepemilikan' => $validated['aset_kepemilikan'],
|
||||
'bantuan_lain' => $validated['bantuan_lain'],
|
||||
'usia' => $validated['usia'],
|
||||
'kondisi_rumah' => $validated['kondisi_rumah'],
|
||||
'meteran_listrik' => $validated['meteran_listrik'],
|
||||
'sumber_air' => $validated['sumber_air'],
|
||||
'pekerjaan' => $validated['pekerjaan'],
|
||||
'penghasilan' => $validated['penghasilan'],
|
||||
'jumlah_tanggungan' => $validated['jumlah_tanggungan'],
|
||||
'aset_pilihan' => $validated['aset_pilihan'] ?? [],
|
||||
'aset_manual' => $validated['aset_manual'] ?? '',
|
||||
'bantuan_lain' => $validated['bantuan_lain'],
|
||||
'usia' => $validated['usia'],
|
||||
'status_perkawinan' => $validated['status_perkawinan'],
|
||||
'lantai_rumah' => $validated['lantai_rumah'],
|
||||
'dinding_rumah' => $validated['dinding_rumah'],
|
||||
'atap_rumah' => $validated['atap_rumah'],
|
||||
'luas_rumah_m2' => $validated['luas_rumah_m2'],
|
||||
'status_kepemilikan_rumah' => $validated['status_kepemilikan_rumah'],
|
||||
'meteran_listrik' => $validated['meteran_listrik'],
|
||||
'sumber_air' => $validated['sumber_air'],
|
||||
];
|
||||
|
||||
$prediction = $this->mlService->getPrediction($predictionData);
|
||||
|
|
@ -188,7 +225,7 @@ public function show(CalonPenerima $calonPenerima)
|
|||
{
|
||||
$user = Auth::user();
|
||||
|
||||
if ($calonPenerima->user_id !== $user->id) {
|
||||
if ((int) $calonPenerima->user_id !== (int) $user->id) {
|
||||
abort(403, 'Unauthorized action.');
|
||||
}
|
||||
|
||||
|
|
@ -203,7 +240,7 @@ public function edit(CalonPenerima $calonPenerima)
|
|||
{
|
||||
$user = Auth::user();
|
||||
|
||||
if ($calonPenerima->user_id !== $user->id || ($calonPenerima->tracking_status ?? 'draft') !== 'draft') {
|
||||
if ((int) $calonPenerima->user_id !== (int) $user->id || ($calonPenerima->tracking_status ?? 'draft') !== 'draft') {
|
||||
abort(403, 'Data yang sudah diajukan tidak dapat diubah.');
|
||||
}
|
||||
|
||||
|
|
@ -214,7 +251,7 @@ public function update(Request $request, CalonPenerima $calonPenerima)
|
|||
{
|
||||
$user = Auth::user();
|
||||
|
||||
if ($calonPenerima->user_id !== $user->id || ($calonPenerima->tracking_status ?? 'draft') !== 'draft') {
|
||||
if ((int) $calonPenerima->user_id !== (int) $user->id || ($calonPenerima->tracking_status ?? 'draft') !== 'draft') {
|
||||
abort(403, 'Data yang sudah diajukan tidak dapat diubah.');
|
||||
}
|
||||
|
||||
|
|
@ -224,34 +261,40 @@ public function update(Request $request, CalonPenerima $calonPenerima)
|
|||
}
|
||||
|
||||
$validated = $request->validate([
|
||||
'rt_id' => 'nullable|exists:rts,id',
|
||||
'no_kk' => 'required|digits:16|unique:calon_penerimas,no_kk,' . $calonPenerima->id,
|
||||
'nik' => 'required|digits:16|unique:calon_penerimas,nik,' . $calonPenerima->id,
|
||||
'nama_lengkap' => 'required|string|max:255',
|
||||
'jenis_kelamin' => 'required|in:Laki-laki,Perempuan',
|
||||
'tempat_lahir' => 'required|string|max:255',
|
||||
'tanggal_lahir' => 'required|date',
|
||||
'alamat' => 'required|string',
|
||||
'desa' => 'nullable|string|max:255',
|
||||
'pekerjaan' => 'required|string',
|
||||
'penghasilan' => 'required|numeric|min:0',
|
||||
'jumlah_tanggungan' => 'required|integer|min:0',
|
||||
'aset_kepemilikan' => 'required|string',
|
||||
'bantuan_lain' => 'required|in:ya,tidak',
|
||||
'usia' => 'required|integer|min:17|max:100',
|
||||
'status_perkawinan' => 'required|string',
|
||||
'kondisi_rumah' => 'required|in:Layak,Sedang,Tidak Layak',
|
||||
'meteran_listrik' => 'required|in:450VA,900VA,1300VA+',
|
||||
'sumber_air' => 'required|in:PDAM,Sumur,Sungai',
|
||||
'foto_rumah_depan' => 'nullable|image|max:2048',
|
||||
'foto_rumah_belakang' => 'nullable|image|max:2048',
|
||||
'foto_rumah_kanan' => 'nullable|image|max:2048',
|
||||
'foto_rumah_kiri' => 'nullable|image|max:2048',
|
||||
'foto_kk' => 'nullable|image|max:2048',
|
||||
'foto_ktp' => 'nullable|image|max:2048',
|
||||
'foto_rekening_listrik' => 'nullable|mimes:jpg,jpeg,png,pdf|max:2048',
|
||||
'foto_meteran_air' => 'nullable|image|max:2048',
|
||||
'dokumen_pendukung' => 'nullable|mimes:jpg,jpeg,png,pdf,doc,docx|max:5120',
|
||||
'rt_id' => 'nullable|exists:rts,id',
|
||||
'no_kk' => 'required|digits:16|unique:calon_penerimas,no_kk,' . $calonPenerima->id,
|
||||
'nik' => 'required|digits:16|unique:calon_penerimas,nik,' . $calonPenerima->id,
|
||||
'nama_lengkap' => 'required|string|max:255',
|
||||
'jenis_kelamin' => 'required|in:Laki-laki,Perempuan',
|
||||
'tempat_lahir' => 'required|string|max:255',
|
||||
'tanggal_lahir' => 'required|date',
|
||||
'alamat' => 'required|string',
|
||||
'desa' => 'nullable|string|max:255',
|
||||
'pekerjaan' => 'required|string',
|
||||
'penghasilan' => 'required|numeric|min:0',
|
||||
'jumlah_tanggungan' => 'required|integer|min:0',
|
||||
'aset_pilihan' => 'nullable|array',
|
||||
'aset_pilihan.*' => 'nullable|string',
|
||||
'aset_manual' => 'nullable|string|max:255',
|
||||
'bantuan_lain' => 'required|in:ya,tidak',
|
||||
'usia' => 'required|integer|min:17|max:100',
|
||||
'status_perkawinan' => 'required|string',
|
||||
'lantai_rumah' => 'required|string',
|
||||
'dinding_rumah' => 'required|string',
|
||||
'atap_rumah' => 'required|string',
|
||||
'luas_rumah_m2' => 'required|integer|min:1',
|
||||
'status_kepemilikan_rumah'=> 'required|string',
|
||||
'meteran_listrik' => 'required|in:450VA,900VA,1300VA+',
|
||||
'sumber_air' => 'required|in:PDAM,Sumur,Sungai',
|
||||
'foto_rumah_depan' => 'nullable|image',
|
||||
'foto_rumah_belakang' => 'nullable|image',
|
||||
'foto_rumah_kanan' => 'nullable|image',
|
||||
'foto_rumah_kiri' => 'nullable|image',
|
||||
'foto_kk' => 'nullable|image',
|
||||
'foto_ktp' => 'nullable|image',
|
||||
'foto_rekening_listrik' => 'nullable|mimes:jpg,jpeg,png,pdf',
|
||||
'foto_meteran_air' => 'nullable|image',
|
||||
'dokumen_pendukung' => 'nullable|mimes:jpg,jpeg,png,pdf,doc,docx',
|
||||
], [
|
||||
'nik.unique' => 'NIK sudah pernah diinput.',
|
||||
'no_kk.unique' => 'No. KK sudah pernah diinput.',
|
||||
|
|
@ -259,6 +302,16 @@ public function update(Request $request, CalonPenerima $calonPenerima)
|
|||
|
||||
$validated['rt_id'] = $myRtId;
|
||||
|
||||
$validated['aset_kepemilikan'] = $this->buildAsetString(
|
||||
$validated['aset_pilihan'] ?? [],
|
||||
$validated['aset_manual'] ?? ''
|
||||
);
|
||||
|
||||
$validated['kondisi_rumah'] = $this->deriveKondisiRumah(
|
||||
$validated['lantai_rumah'],
|
||||
$validated['dinding_rumah']
|
||||
);
|
||||
|
||||
$rt = RT::with('dusun')->find($myRtId);
|
||||
if ($rt && $rt->dusun) {
|
||||
$validated['desa'] = $rt->dusun->nama_dusun;
|
||||
|
|
@ -268,12 +321,23 @@ public function update(Request $request, CalonPenerima $calonPenerima)
|
|||
|
||||
DB::beginTransaction();
|
||||
try {
|
||||
// Handle upload foto — hapus lama kalau ada yang baru
|
||||
$fotoFields = [
|
||||
$fotoImages = [
|
||||
'foto_rumah_depan', 'foto_rumah_belakang', 'foto_rumah_kanan', 'foto_rumah_kiri',
|
||||
'foto_kk', 'foto_ktp', 'foto_rekening_listrik', 'foto_meteran_air', 'dokumen_pendukung',
|
||||
'foto_kk', 'foto_ktp', 'foto_meteran_air',
|
||||
];
|
||||
foreach ($fotoFields as $field) {
|
||||
$fotoFiles = [
|
||||
'foto_rekening_listrik', 'dokumen_pendukung',
|
||||
];
|
||||
|
||||
foreach ($fotoImages as $field) {
|
||||
if ($request->hasFile($field)) {
|
||||
if ($calonPenerima->$field) {
|
||||
Storage::disk('public')->delete($calonPenerima->$field);
|
||||
}
|
||||
$validated[$field] = $this->simpanFotoKompres($request->file($field));
|
||||
}
|
||||
}
|
||||
foreach ($fotoFiles as $field) {
|
||||
if ($request->hasFile($field)) {
|
||||
if ($calonPenerima->$field) {
|
||||
Storage::disk('public')->delete($calonPenerima->$field);
|
||||
|
|
@ -284,34 +348,6 @@ public function update(Request $request, CalonPenerima $calonPenerima)
|
|||
|
||||
$calonPenerima->update($validated);
|
||||
|
||||
/*
|
||||
// Uncomment kalau mau update ulang prediksi ML saat edit
|
||||
$predictionData = [
|
||||
'pekerjaan' => $validated['pekerjaan'],
|
||||
'penghasilan' => $validated['penghasilan'],
|
||||
'jumlah_tanggungan' => $validated['jumlah_tanggungan'],
|
||||
'aset_kepemilikan' => $validated['aset_kepemilikan'],
|
||||
'bantuan_lain' => $validated['bantuan_lain'],
|
||||
'usia' => $validated['usia'],
|
||||
'kondisi_rumah' => $validated['kondisi_rumah'],
|
||||
'meteran_listrik' => $validated['meteran_listrik'],
|
||||
'sumber_air' => $validated['sumber_air'],
|
||||
];
|
||||
$prediction = $this->mlService->getPrediction($predictionData);
|
||||
if ($prediction) {
|
||||
PrediksiKelayakan::updateOrCreate(
|
||||
['calon_penerima_id' => $calonPenerima->id],
|
||||
[
|
||||
'probability' => $prediction['probability'],
|
||||
'recommendation' => $prediction['recommendation'],
|
||||
'kondisi_rumah' => $validated['kondisi_rumah'],
|
||||
'meteran_listrik'=> $validated['meteran_listrik'],
|
||||
'sumber_air' => $validated['sumber_air'],
|
||||
]
|
||||
);
|
||||
}
|
||||
*/
|
||||
|
||||
DB::commit();
|
||||
|
||||
return redirect()->route('rt.calon-penerima.index')
|
||||
|
|
@ -326,7 +362,7 @@ public function ajukan(CalonPenerima $calonPenerima)
|
|||
{
|
||||
$user = Auth::user();
|
||||
|
||||
if ($calonPenerima->user_id !== $user->id) {
|
||||
if ((int) $calonPenerima->user_id !== (int) $user->id) {
|
||||
abort(403, 'Unauthorized action.');
|
||||
}
|
||||
|
||||
|
|
@ -345,11 +381,10 @@ public function destroy(CalonPenerima $calonPenerima)
|
|||
{
|
||||
$user = Auth::user();
|
||||
|
||||
if ($calonPenerima->user_id !== $user->id || ($calonPenerima->tracking_status ?? 'draft') !== 'draft') {
|
||||
if ((int) $calonPenerima->user_id !== (int) $user->id || ($calonPenerima->tracking_status ?? 'draft') !== 'draft') {
|
||||
abort(403, 'Data yang sudah diajukan tidak dapat dihapus.');
|
||||
}
|
||||
|
||||
// Hapus foto dari storage
|
||||
$fotoFields = [
|
||||
'foto_rumah_depan', 'foto_rumah_belakang', 'foto_rumah_kanan', 'foto_rumah_kiri',
|
||||
'foto_kk', 'foto_ktp', 'foto_rekening_listrik', 'foto_meteran_air', 'dokumen_pendukung',
|
||||
|
|
@ -366,6 +401,53 @@ public function destroy(CalonPenerima $calonPenerima)
|
|||
->with('success', 'Data calon penerima berhasil dihapus!');
|
||||
}
|
||||
|
||||
// ── PRIVATE HELPERS ───────────────────────────────────────────────────────
|
||||
|
||||
private function simpanFotoKompres(\Illuminate\Http\UploadedFile $file): string
|
||||
{
|
||||
return $file->store('dokumen-warga', 'public');
|
||||
}
|
||||
|
||||
private function buildAsetString(array $pilihan, string $manual): string
|
||||
{
|
||||
$items = array_filter(array_map('trim', $pilihan));
|
||||
|
||||
if ($manual !== '') {
|
||||
foreach (explode(',', $manual) as $item) {
|
||||
$item = trim($item);
|
||||
if ($item !== '') $items[] = $item;
|
||||
}
|
||||
}
|
||||
|
||||
$items = array_unique($items);
|
||||
return count($items) > 0 ? implode(', ', $items) : 'Tidak Punya';
|
||||
}
|
||||
|
||||
private function deriveKondisiRumah(string $lantai, string $dinding): string
|
||||
{
|
||||
$skorLantai = match($lantai) {
|
||||
'Tanah' => 3,
|
||||
'Papan/Kayu' => 2,
|
||||
'Semen Kasar' => 2,
|
||||
'Keramik Murah' => 1,
|
||||
default => 0,
|
||||
};
|
||||
|
||||
$skorDinding = match($dinding) {
|
||||
'Bambu' => 3,
|
||||
'Anyaman Bambu/Gedek' => 3,
|
||||
'Kayu/Papan' => 2,
|
||||
'Tembok Tidak Plester' => 1,
|
||||
default => 0,
|
||||
};
|
||||
|
||||
$total = $skorLantai + $skorDinding;
|
||||
|
||||
if ($total >= 4) return 'Tidak Layak';
|
||||
if ($total >= 1) return 'Sedang';
|
||||
return 'Layak';
|
||||
}
|
||||
|
||||
private function getPredictionExplanation($calonPenerima): array
|
||||
{
|
||||
$positive = [];
|
||||
|
|
@ -373,7 +455,10 @@ private function getPredictionExplanation($calonPenerima): array
|
|||
|
||||
if (strtolower($calonPenerima->pekerjaan) === 'tidak bekerja') {
|
||||
$positive[] = 'Tidak bekerja';
|
||||
} elseif (in_array(strtolower($calonPenerima->pekerjaan), ['buruh harian', 'buruh', 'petani', 'nelayan'])) {
|
||||
} elseif (in_array(strtolower($calonPenerima->pekerjaan), [
|
||||
'buruh harian lepas', 'buruh harian', 'buruh',
|
||||
'petani kecil', 'petani', 'mengurus rumah tangga',
|
||||
])) {
|
||||
$positive[] = 'Pekerjaan tergolong rentan secara ekonomi';
|
||||
} else {
|
||||
$negative[] = 'Memiliki pekerjaan yang relatif lebih stabil';
|
||||
|
|
@ -395,11 +480,12 @@ private function getPredictionExplanation($calonPenerima): array
|
|||
$negative[] = 'Jumlah tanggungan sedikit';
|
||||
}
|
||||
|
||||
$aset = strtolower($calonPenerima->aset_kepemilikan);
|
||||
if (str_contains($aset, 'mobil')) $negative[] = 'Memiliki aset bernilai tinggi (mobil)';
|
||||
if (str_contains($aset, 'motor')) $negative[] = 'Memiliki aset kendaraan bermotor';
|
||||
if (str_contains($aset, 'rumah')) $negative[] = 'Memiliki aset rumah';
|
||||
if (in_array($aset, ['tidak ada', '-', 'tidak punya'])) $positive[] = 'Tidak memiliki aset berarti';
|
||||
$aset = strtolower($calonPenerima->aset_kepemilikan ?? '');
|
||||
if (str_contains($aset, 'aset lengkap')) $negative[] = 'Memiliki aset bernilai tinggi';
|
||||
elseif (str_contains($aset, 'motor + tanah')) $negative[] = 'Memiliki motor dan tanah';
|
||||
elseif (str_contains($aset, 'motor + emas')) $negative[] = 'Memiliki motor dan emas';
|
||||
elseif (str_contains($aset, 'motor')) $negative[] = 'Memiliki kendaraan bermotor';
|
||||
elseif (str_contains($aset, 'tidak punya')) $positive[] = 'Tidak memiliki aset berarti';
|
||||
|
||||
if (strtolower($calonPenerima->bantuan_lain) === 'ya') {
|
||||
$negative[] = 'Sudah menerima bantuan lain';
|
||||
|
|
@ -413,30 +499,57 @@ private function getPredictionExplanation($calonPenerima): array
|
|||
$positive[] = 'Usia cukup rentan';
|
||||
}
|
||||
|
||||
// Parameter baru
|
||||
if (strtolower($calonPenerima->kondisi_rumah ?? '') === 'tidak layak') {
|
||||
$positive[] = 'Kondisi rumah tidak layak huni';
|
||||
} elseif (strtolower($calonPenerima->kondisi_rumah ?? '') === 'sedang') {
|
||||
$positive[] = 'Kondisi rumah sedang';
|
||||
} else {
|
||||
$negative[] = 'Kondisi rumah layak';
|
||||
}
|
||||
$lantai = $calonPenerima->lantai_rumah ?? '';
|
||||
match($lantai) {
|
||||
'Tanah' => $positive[] = 'Lantai rumah masih tanah',
|
||||
'Papan/Kayu' => $positive[] = 'Lantai rumah papan/kayu',
|
||||
'Semen Kasar' => $positive[] = 'Lantai rumah semen kasar',
|
||||
'Keramik Murah' => $positive[] = 'Lantai rumah keramik murah',
|
||||
'Keramik' => $negative[] = 'Lantai rumah sudah keramik',
|
||||
default => null,
|
||||
};
|
||||
|
||||
if (($calonPenerima->meteran_listrik ?? '') === '450VA') {
|
||||
$positive[] = 'Meteran listrik 450VA (daya rendah)';
|
||||
} elseif (($calonPenerima->meteran_listrik ?? '') === '900VA') {
|
||||
$positive[] = 'Meteran listrik 900VA';
|
||||
} else {
|
||||
$negative[] = 'Meteran listrik 1300VA ke atas';
|
||||
}
|
||||
$dinding = $calonPenerima->dinding_rumah ?? '';
|
||||
match($dinding) {
|
||||
'Bambu' => $positive[] = 'Dinding rumah bambu',
|
||||
'Anyaman Bambu/Gedek' => $positive[] = 'Dinding rumah anyaman bambu/gedek',
|
||||
'Kayu/Papan' => $positive[] = 'Dinding rumah kayu/papan',
|
||||
'Tembok Tidak Plester' => $positive[] = 'Dinding rumah tembok belum diplester',
|
||||
'Tembok Plester' => $negative[] = 'Dinding rumah tembok plester',
|
||||
default => null,
|
||||
};
|
||||
|
||||
if (strtolower($calonPenerima->sumber_air ?? '') === 'sungai') {
|
||||
$positive[] = 'Sumber air dari sungai (kurang layak)';
|
||||
} elseif (strtolower($calonPenerima->sumber_air ?? '') === 'sumur') {
|
||||
$positive[] = 'Sumber air dari sumur';
|
||||
} else {
|
||||
$negative[] = 'Sumber air PDAM (layak)';
|
||||
}
|
||||
$atap = $calonPenerima->atap_rumah ?? '';
|
||||
match($atap) {
|
||||
'Bambu/Jerami' => $positive[] = 'Atap rumah bambu/jerami',
|
||||
'Seng' => $positive[] = 'Atap rumah seng',
|
||||
'Campuran Seng+Genteng' => $positive[] = 'Atap rumah campuran seng dan genteng',
|
||||
'Genteng' => $negative[] = 'Atap rumah sudah genteng',
|
||||
'Genteng Beton' => $negative[] = 'Atap rumah genteng beton',
|
||||
default => null,
|
||||
};
|
||||
|
||||
$statusRumah = $calonPenerima->status_kepemilikan_rumah ?? '';
|
||||
match($statusRumah) {
|
||||
'Menumpang' => $positive[] = 'Status rumah menumpang',
|
||||
'Sewa/Kontrak' => $positive[] = 'Status rumah sewa/kontrak',
|
||||
'Milik Sendiri'=> $negative[] = 'Memiliki rumah sendiri',
|
||||
default => null,
|
||||
};
|
||||
|
||||
match($calonPenerima->meteran_listrik ?? '') {
|
||||
'450VA' => $positive[] = 'Meteran listrik 450 VA',
|
||||
'900VA' => $positive[] = 'Meteran listrik 900 VA',
|
||||
'1300VA+' => $negative[] = 'Meteran listrik 1300 VA ke atas',
|
||||
default => null,
|
||||
};
|
||||
|
||||
match(strtolower($calonPenerima->sumber_air ?? '')) {
|
||||
'sungai' => $positive[] = 'Sumber air dari sungai',
|
||||
'sumur' => $positive[] = 'Sumber air dari sumur',
|
||||
'pdam' => $negative[] = 'Sumber air PDAM',
|
||||
default => null,
|
||||
};
|
||||
|
||||
$probability = $calonPenerima->prediksiKelayakan->probability ?? 0;
|
||||
$probability = $probability <= 1 ? $probability * 100 : $probability;
|
||||
|
|
|
|||
|
|
@ -22,28 +22,24 @@ public function index(Request $request)
|
|||
if (!$rt) {
|
||||
$laporans = collect();
|
||||
$stats = [
|
||||
'total' => 0,
|
||||
'total' => 0,
|
||||
'diterima' => 0,
|
||||
'ditolak' => 0,
|
||||
'ditolak' => 0,
|
||||
];
|
||||
|
||||
return view('rt.laporan', compact('laporans', 'stats'));
|
||||
}
|
||||
|
||||
$nomorRt = $rt->nomor_rt;
|
||||
$namaDusun = $rt->dusun->nama_dusun ?? null;
|
||||
|
||||
$laporans = PenerimaFinal::query()
|
||||
->where('nomor_rt', $nomorRt)
|
||||
->where('nama_dusun', $namaDusun)
|
||||
->where('rt_id', $rt->id)
|
||||
->whereIn('status_verifikasi', ['disetujui', 'ditolak'])
|
||||
->orderByDesc('tanggal_penetapan')
|
||||
->get();
|
||||
|
||||
$stats = [
|
||||
'total' => $laporans->count(),
|
||||
'total' => $laporans->count(),
|
||||
'diterima' => $laporans->where('status_verifikasi', 'disetujui')->count(),
|
||||
'ditolak' => $laporans->where('status_verifikasi', 'ditolak')->count(),
|
||||
'ditolak' => $laporans->where('status_verifikasi', 'ditolak')->count(),
|
||||
];
|
||||
|
||||
return view('rt.laporan', compact('laporans', 'stats'));
|
||||
|
|
|
|||
|
|
@ -0,0 +1,60 @@
|
|||
<?php
|
||||
|
||||
namespace App\Http\Middleware;
|
||||
|
||||
use Closure;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Carbon\Carbon;
|
||||
|
||||
class AutoArsipMiddleware
|
||||
{
|
||||
public function handle(Request $request, Closure $next)
|
||||
{
|
||||
$this->jalankanAutoArsip();
|
||||
return $next($request);
|
||||
}
|
||||
|
||||
private function jalankanAutoArsip(): void
|
||||
{
|
||||
$sekarang = Carbon::now('Asia/Jakarta');
|
||||
|
||||
// ── 1. AUTO ARSIP ──────────────────────────────────────────────
|
||||
// Tiap tanggal 1, arsipkan semua data yang periode_bulan-nya
|
||||
// sudah bulan lalu (atau lebih lama) dan belum diarsip
|
||||
if ($sekarang->day === 1) {
|
||||
$batasBulan = $sekarang->copy()->startOfMonth(); // awal bulan ini
|
||||
|
||||
DB::table('calon_penerimas')
|
||||
->whereIn('tracking_status', ['terkirim', 'sedang_validasi', 'selesai'])
|
||||
->whereNull('arsip_tahun')
|
||||
->whereNotNull('periode_bulan')
|
||||
->where('periode_bulan', '<', $batasBulan->toDateString())
|
||||
->update([
|
||||
'arsip_tahun' => $batasBulan->copy()->subMonth()->year,
|
||||
'updated_at' => now(),
|
||||
]);
|
||||
}
|
||||
|
||||
// ── 2. AUTO HAPUS ARSIP ────────────────────────────────────────
|
||||
// Tiap tanggal 1 Januari, hapus permanen arsip tahun lalu
|
||||
if ($sekarang->day === 1 && $sekarang->month === 1) {
|
||||
$tahunHapus = $sekarang->year - 1;
|
||||
|
||||
// Hapus prediksi terkait dulu (foreign key)
|
||||
$idArsip = DB::table('calon_penerimas')
|
||||
->where('arsip_tahun', $tahunHapus)
|
||||
->pluck('id');
|
||||
|
||||
if ($idArsip->isNotEmpty()) {
|
||||
DB::table('prediksi_kelayakans')
|
||||
->whereIn('calon_penerima_id', $idArsip)
|
||||
->delete();
|
||||
|
||||
DB::table('calon_penerimas')
|
||||
->where('arsip_tahun', $tahunHapus)
|
||||
->delete();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -10,41 +10,49 @@ class CalonPenerima extends Model
|
|||
use HasFactory;
|
||||
|
||||
protected $fillable = [
|
||||
'user_id',
|
||||
'rt_id',
|
||||
'no_kk',
|
||||
'nik',
|
||||
'nama_lengkap',
|
||||
'jenis_kelamin',
|
||||
'tempat_lahir',
|
||||
'tanggal_lahir',
|
||||
'alamat',
|
||||
'desa',
|
||||
'pekerjaan',
|
||||
'penghasilan',
|
||||
'jumlah_tanggungan',
|
||||
'aset_kepemilikan',
|
||||
'kondisi_rumah',
|
||||
'meteran_listrik',
|
||||
'sumber_air',
|
||||
'bantuan_lain',
|
||||
'usia',
|
||||
'status_perkawinan',
|
||||
'status_verifikasi',
|
||||
'catatan_admin',
|
||||
'foto_rumah_depan',
|
||||
'foto_rumah_belakang',
|
||||
'foto_rumah_kanan',
|
||||
'foto_rumah_kiri',
|
||||
'foto_kk',
|
||||
'foto_ktp',
|
||||
'foto_rekening_listrik',
|
||||
'foto_meteran_air',
|
||||
'dokumen_pendukung',
|
||||
'user_id',
|
||||
'rt_id',
|
||||
'no_kk',
|
||||
'nik',
|
||||
'nama_lengkap',
|
||||
'jenis_kelamin',
|
||||
'tempat_lahir',
|
||||
'tanggal_lahir',
|
||||
'alamat',
|
||||
'desa',
|
||||
'pekerjaan',
|
||||
'penghasilan',
|
||||
'jumlah_tanggungan',
|
||||
'aset_kepemilikan',
|
||||
'kondisi_rumah',
|
||||
'lantai_rumah',
|
||||
'dinding_rumah',
|
||||
'atap_rumah',
|
||||
'luas_rumah_m2',
|
||||
'status_kepemilikan_rumah',
|
||||
'meteran_listrik',
|
||||
'sumber_air',
|
||||
'bantuan_lain',
|
||||
'usia',
|
||||
'status_perkawinan',
|
||||
'status_verifikasi',
|
||||
'tracking_status',
|
||||
'catatan_admin',
|
||||
'arsip_tahun',
|
||||
'periode_bulan',
|
||||
'foto_rumah_depan',
|
||||
'foto_rumah_belakang',
|
||||
'foto_rumah_kanan',
|
||||
'foto_rumah_kiri',
|
||||
'foto_kk',
|
||||
'foto_ktp',
|
||||
'foto_rekening_listrik',
|
||||
'foto_meteran_air',
|
||||
'dokumen_pendukung',
|
||||
];
|
||||
|
||||
protected $casts = [
|
||||
'penghasilan' => 'decimal:2',
|
||||
'penghasilan' => 'decimal:2',
|
||||
'tanggal_lahir' => 'date',
|
||||
];
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,12 @@
|
|||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
class PenerimaFinalArsip extends Model
|
||||
{
|
||||
protected $table = 'penerima_final_arsip';
|
||||
protected $guarded = [];
|
||||
protected $casts = ['tanggal_penetapan' => 'date'];
|
||||
}
|
||||
|
|
@ -0,0 +1,26 @@
|
|||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
class PeriodeDanaBlt extends Model
|
||||
{
|
||||
protected $table = 'periode_dana_blt';
|
||||
|
||||
protected $fillable = [
|
||||
'periode',
|
||||
'dana_awal',
|
||||
'jumlah_penerima',
|
||||
'dana_terpakai',
|
||||
'dana_sisa',
|
||||
'keterangan',
|
||||
];
|
||||
|
||||
protected $casts = [
|
||||
'dana_awal' => 'integer',
|
||||
'jumlah_penerima' => 'integer',
|
||||
'dana_terpakai' => 'integer',
|
||||
'dana_sisa' => 'integer',
|
||||
];
|
||||
}
|
||||
|
|
@ -0,0 +1,39 @@
|
|||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
class RiwayatPenerima extends Model
|
||||
{
|
||||
protected $table = 'riwayat_penerima';
|
||||
|
||||
protected $fillable = [
|
||||
'periode',
|
||||
'nama_lengkap',
|
||||
'nik',
|
||||
'no_kk',
|
||||
'rt_id',
|
||||
'nomor_rt',
|
||||
'nama_dusun',
|
||||
'pekerjaan',
|
||||
'penghasilan',
|
||||
'jumlah_tanggungan',
|
||||
'aset_kepemilikan',
|
||||
'kondisi_rumah',
|
||||
'meteran_listrik',
|
||||
'sumber_air',
|
||||
'bantuan_lain',
|
||||
'usia',
|
||||
'probability',
|
||||
'status_verifikasi',
|
||||
'tanggal_penetapan',
|
||||
'jumlah_bantuan',
|
||||
'di_arsipkan_pada',
|
||||
];
|
||||
|
||||
protected $casts = [
|
||||
'tanggal_penetapan' => 'date',
|
||||
'di_arsipkan_pada' => 'datetime',
|
||||
];
|
||||
}
|
||||
|
|
@ -16,6 +16,10 @@
|
|||
'is_active' => \App\Http\Middleware\EnsureUserIsActive::class,
|
||||
]);
|
||||
|
||||
$middleware->web(append: [
|
||||
\App\Http\Middleware\AutoArsipMiddleware::class,
|
||||
]);
|
||||
|
||||
})
|
||||
->withExceptions(function (Exceptions $exceptions): void {
|
||||
//
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@
|
|||
"require": {
|
||||
"php": "^8.2",
|
||||
"barryvdh/laravel-dompdf": "^3.1",
|
||||
"intervention/image": "^3.11",
|
||||
"laravel/framework": "^12.0",
|
||||
"laravel/tinker": "^2.10.1",
|
||||
"maatwebsite/excel": "*"
|
||||
|
|
@ -90,4 +91,4 @@
|
|||
},
|
||||
"minimum-stability": "stable",
|
||||
"prefer-stable": true
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@
|
|||
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
|
||||
"This file is @generated automatically"
|
||||
],
|
||||
"content-hash": "e0c1bd04a3bb8afaef35697676fa0752",
|
||||
"content-hash": "3074c23690bd98b29e2eb7fdb9df343a",
|
||||
"packages": [
|
||||
{
|
||||
"name": "barryvdh/laravel-dompdf",
|
||||
|
|
@ -1502,6 +1502,150 @@
|
|||
],
|
||||
"time": "2025-08-22T14:27:06+00:00"
|
||||
},
|
||||
{
|
||||
"name": "intervention/gif",
|
||||
"version": "4.2.4",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/Intervention/gif.git",
|
||||
"reference": "c3598a16ebe7690cd55640c44144a9df383ea73c"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/Intervention/gif/zipball/c3598a16ebe7690cd55640c44144a9df383ea73c",
|
||||
"reference": "c3598a16ebe7690cd55640c44144a9df383ea73c",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"php": "^8.1"
|
||||
},
|
||||
"require-dev": {
|
||||
"phpstan/phpstan": "^2.1",
|
||||
"phpunit/phpunit": "^10.0 || ^11.0 || ^12.0",
|
||||
"slevomat/coding-standard": "~8.0",
|
||||
"squizlabs/php_codesniffer": "^3.8"
|
||||
},
|
||||
"type": "library",
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"Intervention\\Gif\\": "src"
|
||||
}
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"MIT"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "Oliver Vogel",
|
||||
"email": "oliver@intervention.io",
|
||||
"homepage": "https://intervention.io/"
|
||||
}
|
||||
],
|
||||
"description": "Native PHP GIF Encoder/Decoder",
|
||||
"homepage": "https://github.com/intervention/gif",
|
||||
"keywords": [
|
||||
"animation",
|
||||
"gd",
|
||||
"gif",
|
||||
"image"
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/Intervention/gif/issues",
|
||||
"source": "https://github.com/Intervention/gif/tree/4.2.4"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
"url": "https://paypal.me/interventionio",
|
||||
"type": "custom"
|
||||
},
|
||||
{
|
||||
"url": "https://github.com/Intervention",
|
||||
"type": "github"
|
||||
},
|
||||
{
|
||||
"url": "https://ko-fi.com/interventionphp",
|
||||
"type": "ko_fi"
|
||||
}
|
||||
],
|
||||
"time": "2026-01-04T09:27:23+00:00"
|
||||
},
|
||||
{
|
||||
"name": "intervention/image",
|
||||
"version": "3.11.8",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/Intervention/image.git",
|
||||
"reference": "cf04c8dd245697f701057c13d4bfe140d584e738"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/Intervention/image/zipball/cf04c8dd245697f701057c13d4bfe140d584e738",
|
||||
"reference": "cf04c8dd245697f701057c13d4bfe140d584e738",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"ext-mbstring": "*",
|
||||
"intervention/gif": "^4.2",
|
||||
"php": "^8.1"
|
||||
},
|
||||
"require-dev": {
|
||||
"mockery/mockery": "^1.6",
|
||||
"phpstan/phpstan": "^2.1",
|
||||
"phpunit/phpunit": "^10.0 || ^11.0 || ^12.0",
|
||||
"slevomat/coding-standard": "~8.0",
|
||||
"squizlabs/php_codesniffer": "^4"
|
||||
},
|
||||
"suggest": {
|
||||
"ext-exif": "Recommended to be able to read EXIF data properly."
|
||||
},
|
||||
"type": "library",
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"Intervention\\Image\\": "src"
|
||||
}
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"MIT"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "Oliver Vogel",
|
||||
"email": "oliver@intervention.io",
|
||||
"homepage": "https://intervention.io"
|
||||
}
|
||||
],
|
||||
"description": "PHP Image Processing",
|
||||
"homepage": "https://image.intervention.io",
|
||||
"keywords": [
|
||||
"gd",
|
||||
"image",
|
||||
"imagick",
|
||||
"resize",
|
||||
"thumbnail",
|
||||
"watermark"
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/Intervention/image/issues",
|
||||
"source": "https://github.com/Intervention/image/tree/3.11.8"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
"url": "https://paypal.me/interventionio",
|
||||
"type": "custom"
|
||||
},
|
||||
{
|
||||
"url": "https://github.com/Intervention",
|
||||
"type": "github"
|
||||
},
|
||||
{
|
||||
"url": "https://ko-fi.com/interventionphp",
|
||||
"type": "ko_fi"
|
||||
}
|
||||
],
|
||||
"time": "2026-05-01T08:20:10+00:00"
|
||||
},
|
||||
{
|
||||
"name": "laravel/framework",
|
||||
"version": "v12.56.0",
|
||||
|
|
|
|||
|
|
@ -0,0 +1,32 @@
|
|||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
public function up(): void
|
||||
{
|
||||
Schema::table('calon_penerimas', function (Blueprint $table) {
|
||||
$table->string('lantai_rumah')->nullable()->after('kondisi_rumah');
|
||||
$table->string('dinding_rumah')->nullable()->after('lantai_rumah');
|
||||
$table->string('atap_rumah')->nullable()->after('dinding_rumah');
|
||||
$table->integer('luas_rumah_m2')->nullable()->after('atap_rumah');
|
||||
$table->string('status_kepemilikan_rumah')->nullable()->after('luas_rumah_m2');
|
||||
});
|
||||
}
|
||||
|
||||
public function down(): void
|
||||
{
|
||||
Schema::table('calon_penerimas', function (Blueprint $table) {
|
||||
$table->dropColumn([
|
||||
'lantai_rumah',
|
||||
'dinding_rumah',
|
||||
'atap_rumah',
|
||||
'luas_rumah_m2',
|
||||
'status_kepemilikan_rumah',
|
||||
]);
|
||||
});
|
||||
}
|
||||
};
|
||||
|
|
@ -0,0 +1,40 @@
|
|||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
public function up(): void
|
||||
{
|
||||
Schema::create('penerima_final_arsip', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->string('periode_arsip');
|
||||
$table->unsignedBigInteger('penerima_final_id')->nullable();
|
||||
$table->string('nama_lengkap')->nullable();
|
||||
$table->string('nik')->nullable();
|
||||
$table->string('no_kk')->nullable();
|
||||
$table->string('nomor_rt')->nullable();
|
||||
$table->string('nama_dusun')->nullable();
|
||||
$table->string('pekerjaan')->nullable();
|
||||
$table->bigInteger('penghasilan')->default(0);
|
||||
$table->integer('jumlah_tanggungan')->default(0);
|
||||
$table->string('aset_kepemilikan')->nullable();
|
||||
$table->string('bantuan_lain')->nullable();
|
||||
$table->integer('usia')->nullable();
|
||||
$table->float('probability')->nullable();
|
||||
$table->string('status_verifikasi')->nullable();
|
||||
$table->date('tanggal_penetapan')->nullable();
|
||||
$table->string('periode_bantuan')->nullable();
|
||||
$table->bigInteger('jumlah_bantuan')->default(300000);
|
||||
$table->timestamp('diarsipkan_pada')->useCurrent();
|
||||
$table->timestamps();
|
||||
});
|
||||
}
|
||||
|
||||
public function down(): void
|
||||
{
|
||||
Schema::dropIfExists('penerima_final_arsip');
|
||||
}
|
||||
};
|
||||
|
|
@ -0,0 +1,42 @@
|
|||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
public function up(): void
|
||||
{
|
||||
Schema::create('riwayat_penerima', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->string('periode'); // contoh: "2026-04"
|
||||
$table->string('nama_lengkap');
|
||||
$table->string('nik', 16);
|
||||
$table->string('no_kk', 16)->nullable();
|
||||
$table->unsignedBigInteger('rt_id')->nullable();
|
||||
$table->string('nomor_rt')->nullable();
|
||||
$table->string('nama_dusun')->nullable();
|
||||
$table->string('pekerjaan')->nullable();
|
||||
$table->decimal('penghasilan', 15, 2)->default(0);
|
||||
$table->integer('jumlah_tanggungan')->default(0);
|
||||
$table->string('aset_kepemilikan')->nullable();
|
||||
$table->string('kondisi_rumah')->nullable();
|
||||
$table->string('meteran_listrik')->nullable();
|
||||
$table->string('sumber_air')->nullable();
|
||||
$table->string('bantuan_lain')->default('tidak');
|
||||
$table->integer('usia')->default(0);
|
||||
$table->decimal('probability', 8, 4)->default(0);
|
||||
$table->string('status_verifikasi')->default('disetujui');
|
||||
$table->date('tanggal_penetapan')->nullable();
|
||||
$table->decimal('jumlah_bantuan', 15, 2)->default(0);
|
||||
$table->timestamp('di_arsipkan_pada')->nullable(); // kapan di-reset/arsip
|
||||
$table->timestamps();
|
||||
});
|
||||
}
|
||||
|
||||
public function down(): void
|
||||
{
|
||||
Schema::dropIfExists('riwayat_penerima');
|
||||
}
|
||||
};
|
||||
|
|
@ -0,0 +1,28 @@
|
|||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*/
|
||||
public function up(): void
|
||||
{
|
||||
Schema::table('wargas', function (Blueprint $table) {
|
||||
//
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
Schema::table('wargas', function (Blueprint $table) {
|
||||
$table->year('arsip_tahun')->nullable()->after('tracking_status');
|
||||
});
|
||||
}
|
||||
};
|
||||
|
|
@ -23,3 +23,6 @@
|
|||
RewriteCond %{REQUEST_FILENAME} !-f
|
||||
RewriteRule ^ index.php [L]
|
||||
</IfModule>
|
||||
|
||||
php_value upload_max_filesize 20M
|
||||
php_value post_max_size 25M
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 234 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 98 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 68 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 68 KiB |
|
|
@ -1,37 +1,37 @@
|
|||
<x-app-layout>
|
||||
<x-slot name="header">
|
||||
<div style="display:flex;align-items:center;justify-content:space-between;gap:10px;flex-wrap:wrap;">
|
||||
<div style="display:flex;align-items:center;justify-content:space-between;gap:12px;flex-wrap:wrap;">
|
||||
<div>
|
||||
<h2 style="font-size:16px;font-weight:900;color:#0f172a;letter-spacing:-.02em;">Dashboard Kelurahan</h2>
|
||||
<p style="font-size:11px;color:#94a3b8;margin-top:2px;font-weight:500;">Ringkasan data kelayakan penerima BLT-DD</p>
|
||||
<h2 style="font-size:20px;font-weight:900;color:#0f172a;letter-spacing:-.02em;">Dashboard Admin Perangkat Desa</h2>
|
||||
<p style="font-size:14px;color:#64748b;margin-top:4px;font-weight:500;">Ringkasan data kelayakan penerima BLT-DD</p>
|
||||
</div>
|
||||
<div style="display:flex;align-items:center;gap:8px;flex-wrap:wrap;">
|
||||
<div style="display:flex;align-items:center;gap:10px;flex-wrap:wrap;">
|
||||
{{-- BELL --}}
|
||||
<div style="position:relative;" id="notif-wrap">
|
||||
<button onclick="toggleNotif()"
|
||||
style="position:relative;width:36px;height:36px;background:#fff;border:1.5px solid #e2e8f0;border-radius:11px;display:flex;align-items:center;justify-content:center;cursor:pointer;box-shadow:0 1px 4px rgba(0,0,0,.05);">
|
||||
<svg width="16" height="16" fill="none" stroke="#64748b" viewBox="0 0 24 24">
|
||||
style="position:relative;width:44px;height:44px;background:#fff;border:1.5px solid #e2e8f0;border-radius:12px;display:flex;align-items:center;justify-content:center;cursor:pointer;box-shadow:0 1px 4px rgba(0,0,0,.05);">
|
||||
<svg width="20" height="20" fill="none" stroke="#64748b" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
|
||||
d="M15 17h5l-1.405-1.405A2.032 2.032 0 0118 14.158V11a6 6 0 00-9.33-4.976A6 6 0 006 11v3.159c0 .538-.214 1.055-.595 1.436L4 17h5m6 0v1a3 3 0 11-6 0v-1m6 0H9"/>
|
||||
</svg>
|
||||
<span id="notif-badge"
|
||||
style="display:none;position:absolute;top:-5px;right:-5px;min-width:17px;height:17px;border-radius:99px;background:#ef4444;color:#fff;font-size:9px;font-weight:800;align-items:center;justify-content:center;padding:0 3px;border:2px solid #fff;">0</span>
|
||||
style="display:none;position:absolute;top:-6px;right:-6px;min-width:20px;height:20px;border-radius:99px;background:#ef4444;color:#fff;font-size:11px;font-weight:800;align-items:center;justify-content:center;padding:0 4px;border:2px solid #fff;">0</span>
|
||||
</button>
|
||||
<div id="notif-dropdown"
|
||||
style="display:none;position:absolute;top:calc(100% + 8px);right:0;width:min(310px,90vw);background:#fff;border-radius:18px;border:1.5px solid #f1f5f9;box-shadow:0 20px 56px rgba(0,0,0,.13);z-index:999;overflow:hidden;">
|
||||
<div style="padding:12px 15px;border-bottom:1px solid #f1f5f9;background:#fafbfc;display:flex;align-items:center;justify-content:space-between;">
|
||||
<span style="font-size:12px;font-weight:800;color:#0f172a;">Notifikasi</span>
|
||||
<button onclick="clearNotifs()" style="font-size:10.5px;color:#2563eb;cursor:pointer;font-weight:700;background:none;border:none;">Hapus semua</button>
|
||||
style="display:none;position:absolute;top:calc(100% + 10px);right:0;width:min(340px,92vw);background:#fff;border-radius:18px;border:1.5px solid #f1f5f9;box-shadow:0 20px 56px rgba(0,0,0,.13);z-index:999;overflow:hidden;">
|
||||
<div style="padding:14px 18px;border-bottom:1px solid #f1f5f9;background:#fafbfc;display:flex;align-items:center;justify-content:space-between;">
|
||||
<span style="font-size:15px;font-weight:800;color:#0f172a;">Notifikasi</span>
|
||||
<button onclick="clearNotifs()" style="font-size:14px;color:#2563eb;cursor:pointer;font-weight:700;background:none;border:none;">Hapus semua</button>
|
||||
</div>
|
||||
<div id="notif-list" style="max-height:260px;overflow-y:auto;">
|
||||
<div style="padding:24px 16px;text-align:center;font-size:12px;color:#94a3b8;">Tidak ada notifikasi</div>
|
||||
<div id="notif-list" style="max-height:300px;overflow-y:auto;">
|
||||
<div style="padding:28px 18px;text-align:center;font-size:14px;color:#94a3b8;">Tidak ada notifikasi</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{-- CLOCK --}}
|
||||
<div style="display:inline-flex;align-items:center;gap:6px;background:#fff;border:1.5px solid #e2e8f0;border-radius:11px;padding:7px 12px;font-size:11px;color:#64748b;font-weight:500;">
|
||||
<svg width="13" height="13" fill="none" stroke="#3b82f6" viewBox="0 0 24 24">
|
||||
<div id="admin-clock-wrap" style="display:inline-flex;align-items:center;gap:8px;background:#fff;border:1.5px solid #e2e8f0;border-radius:12px;padding:10px 16px;font-size:13px;color:#64748b;font-weight:600;">
|
||||
<svg width="16" height="16" fill="none" stroke="#3b82f6" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 8v4l3 3m6-3a9 9 0 11-18 0 9 9 0 0118 0z"/>
|
||||
</svg>
|
||||
<span id="admin-clock"></span>
|
||||
|
|
@ -49,31 +49,111 @@
|
|||
/* ── TABLE ── */
|
||||
.tt table{width:100%;border-collapse:collapse;}
|
||||
.tt thead tr{background:#f8fafc;border-bottom:1.5px solid #f1f5f9;}
|
||||
.tt thead th{padding:10px 12px;text-align:left;font-size:9.5px;font-weight:800;color:#94a3b8;white-space:nowrap;}
|
||||
.tt thead th{padding:13px 14px;text-align:left;font-size:12px;font-weight:800;color:#64748b;white-space:nowrap;}
|
||||
.tt tbody tr{border-bottom:1px solid #f8fafc;transition:background .1s;}
|
||||
.tt tbody tr:hover{background:#f8fbff;}
|
||||
.tt tbody td{padding:10px 12px;font-size:12px;color:#374151;}
|
||||
.tt tbody td{padding:13px 14px;font-size:14px;color:#374151;}
|
||||
|
||||
/* notif items */
|
||||
.ni{display:flex;align-items:flex-start;gap:10px;padding:10px 14px;border-bottom:1px solid #f8fafc;}
|
||||
.ni{display:flex;align-items:flex-start;gap:12px;padding:13px 16px;border-bottom:1px solid #f8fafc;}
|
||||
.ni.unread{background:#f0f7ff;}
|
||||
.ni-icon{width:30px;height:30px;border-radius:9px;display:flex;align-items:center;justify-content:center;flex-shrink:0;}
|
||||
.ni-icon svg{width:14px;height:14px;}
|
||||
.ni-txt{font-size:11.5px;font-weight:600;color:#1e293b;line-height:1.4;}
|
||||
.ni-time{font-size:10px;color:#94a3b8;margin-top:2px;}
|
||||
.ni-icon{width:36px;height:36px;border-radius:10px;display:flex;align-items:center;justify-content:center;flex-shrink:0;}
|
||||
.ni-icon svg{width:17px;height:17px;}
|
||||
.ni-txt{font-size:14px;font-weight:600;color:#1e293b;line-height:1.5;}
|
||||
.ni-time{font-size:12px;color:#94a3b8;margin-top:3px;}
|
||||
|
||||
/* ── RESPONSIVE ── */
|
||||
/* ── RESPONSIVE DESKTOP ── */
|
||||
@media (max-width: 1024px) {
|
||||
.adm-stat-grid { grid-template-columns: repeat(2,1fr) !important; }
|
||||
.adm-chart-grid { grid-template-columns: 1fr !important; }
|
||||
}
|
||||
@media (max-width: 600px) {
|
||||
.adm-stat-grid { grid-template-columns: repeat(2,1fr) !important; }
|
||||
.adm-greeting { flex-direction: column !important; align-items: flex-start !important; }
|
||||
#admin-clock { display: none; }
|
||||
|
||||
/* ── RESPONSIVE MOBILE ── */
|
||||
@media (max-width: 768px) {
|
||||
/* Header */
|
||||
.adm-header-title { font-size:17px !important; }
|
||||
.adm-header-sub { font-size:13px !important; }
|
||||
|
||||
/* Jam disembunyikan di mobile */
|
||||
#admin-clock-wrap { display:none !important; }
|
||||
|
||||
/* Notif button lebih mudah dipencet */
|
||||
#notif-wrap button { width:48px !important; height:48px !important; }
|
||||
|
||||
/* Stat cards: 2 kolom proporsional */
|
||||
.adm-stat-grid {
|
||||
grid-template-columns: repeat(2,1fr) !important;
|
||||
gap:10px !important;
|
||||
}
|
||||
.adm-stat-grid > div {
|
||||
padding:16px 14px !important;
|
||||
border-radius:14px !important;
|
||||
}
|
||||
.adm-stat-card-num { font-size:30px !important; line-height:1 !important; }
|
||||
.adm-stat-card-lbl { font-size:13px !important; }
|
||||
.adm-stat-card-sub { font-size:11px !important; }
|
||||
.adm-stat-card-icon { width:44px !important; height:44px !important; }
|
||||
.adm-stat-card-icon svg { width:22px !important; height:22px !important; }
|
||||
|
||||
/* Greeting: susun vertikal, full width */
|
||||
.adm-greeting { flex-direction:column !important; gap:10px !important; }
|
||||
.adm-greeting > div {
|
||||
min-width:unset !important;
|
||||
flex:none !important;
|
||||
width:100% !important;
|
||||
}
|
||||
.adm-greeting-name { font-size:15px !important; }
|
||||
.adm-greeting-sub { font-size:13px !important; }
|
||||
.adm-pending-txt { font-size:14px !important; }
|
||||
.adm-pending-link { font-size:13px !important; }
|
||||
|
||||
/* Chart: stack vertikal */
|
||||
.adm-chart-grid {
|
||||
grid-template-columns:1fr !important;
|
||||
gap:12px !important;
|
||||
}
|
||||
.adm-section-title { font-size:15px !important; }
|
||||
.adm-section-badge { font-size:13px !important; }
|
||||
|
||||
/* Dusun bar */
|
||||
.adm-dusun-name { font-size:14px !important; }
|
||||
.adm-dusun-num { font-size:18px !important; }
|
||||
.adm-dusun-bar { height:8px !important; }
|
||||
|
||||
/* Top 10 table */
|
||||
.tt thead th {
|
||||
font-size:13px !important;
|
||||
padding:12px 14px !important;
|
||||
white-space:nowrap !important;
|
||||
}
|
||||
.tt tbody td {
|
||||
font-size:13px !important;
|
||||
padding:12px 14px !important;
|
||||
}
|
||||
.adm-tbl-name { font-size:14px !important; }
|
||||
.adm-tbl-job { font-size:12px !important; }
|
||||
.adm-tbl-nik { font-size:12px !important; }
|
||||
.adm-tbl-dusun { font-size:12px !important; }
|
||||
.adm-tbl-rt { font-size:12px !important; }
|
||||
.adm-tbl-income { font-size:13px !important; }
|
||||
.adm-tbl-pct { font-size:13px !important; }
|
||||
.adm-tbl-badge { font-size:12px !important; padding:6px 12px !important; }
|
||||
.adm-rank-badge { width:32px !important; height:32px !important; font-size:13px !important; }
|
||||
.adm-avatar-sm { width:34px !important; height:34px !important; font-size:14px !important; }
|
||||
|
||||
/* Tombol Lihat Semua */
|
||||
.adm-lihat-semua {
|
||||
font-size:13px !important;
|
||||
padding:9px 14px !important;
|
||||
min-height:42px !important;
|
||||
}
|
||||
}
|
||||
@media (max-width: 400px) {
|
||||
.adm-stat-grid { grid-template-columns: 1fr !important; }
|
||||
|
||||
@media (max-width: 420px) {
|
||||
/* HP sangat kecil: stat cards tetap 2 kolom, lebih compact */
|
||||
.adm-stat-grid { grid-template-columns: repeat(2,1fr) !important; gap:8px !important; }
|
||||
.adm-stat-card-num { font-size:26px !important; }
|
||||
.adm-stat-grid > div { padding:14px 12px !important; }
|
||||
}
|
||||
</style>
|
||||
|
||||
|
|
@ -109,7 +189,7 @@ function renderNotifs(){
|
|||
if(!list)return;
|
||||
const c=NS.unread();
|
||||
if(badge){badge.textContent=c>9?'9+':c;badge.style.display=c>0?'flex':'none';}
|
||||
if(!NS.items.length){list.innerHTML='<div style="padding:24px 16px;text-align:center;font-size:12px;color:#94a3b8;">Tidak ada notifikasi</div>';return;}
|
||||
if(!NS.items.length){list.innerHTML='<div style="padding:28px 18px;text-align:center;font-size:14px;color:#94a3b8;">Tidak ada notifikasi</div>';return;}
|
||||
list.innerHTML=NS.items.map(n=>`<div class="ni ${n.read?'':'unread'}"><div class="ni-icon" style="background:${n.color}18;"><svg fill="none" stroke="${n.color}" viewBox="0 0 24 24">${n.icon}</svg></div><div><div class="ni-txt">${n.text}</div><div class="ni-time">${n.time}</div></div></div>`).join('');
|
||||
}
|
||||
function toggleNotif(){
|
||||
|
|
@ -142,32 +222,32 @@ function clearNotifs(){NS.clearAll();renderNotifs();}
|
|||
});
|
||||
</script>
|
||||
|
||||
<div style="display:flex;flex-direction:column;gap:14px;">
|
||||
<div style="display:flex;flex-direction:column;gap:16px;">
|
||||
|
||||
{{-- GREETING --}}
|
||||
<div class="adm-greeting" style="display:flex;flex-wrap:wrap;gap:10px;">
|
||||
<div style="display:flex;align-items:center;gap:12px;background:#fff;border:1.5px solid #f1f5f9;border-radius:16px;padding:14px 16px;box-shadow:0 1px 4px rgba(0,0,0,.04);flex:1;min-width:220px;">
|
||||
<div style="width:38px;height:38px;border-radius:50%;background:linear-gradient(135deg,#3b82f6,#2563eb);display:flex;align-items:center;justify-content:center;color:#fff;font-size:14px;font-weight:800;flex-shrink:0;box-shadow:0 4px 12px rgba(37,99,235,.28);">
|
||||
<div class="adm-greeting" style="display:flex;flex-wrap:wrap;gap:12px;">
|
||||
<div style="display:flex;align-items:center;gap:14px;background:#fff;border:1.5px solid #f1f5f9;border-radius:16px;padding:16px 20px;box-shadow:0 1px 4px rgba(0,0,0,.04);flex:1;min-width:240px;">
|
||||
<div style="width:48px;height:48px;border-radius:50%;background:linear-gradient(135deg,#3b82f6,#2563eb);display:flex;align-items:center;justify-content:center;color:#fff;font-size:18px;font-weight:800;flex-shrink:0;box-shadow:0 4px 12px rgba(37,99,235,.28);">
|
||||
{{ strtoupper(substr(Auth::user()->name ?? 'A', 0, 1)) }}
|
||||
</div>
|
||||
<div>
|
||||
<p style="font-size:13px;font-weight:700;color:#0f172a;">Selamat datang, <span style="color:#2563eb;">{{ Auth::user()->name ?? 'Admin' }}</span> 👋</p>
|
||||
<p style="font-size:11px;color:#94a3b8;margin-top:2px;font-weight:500;">Panel admin — kelola dan pantau seluruh data warga.</p>
|
||||
<p class="adm-greeting-name" style="font-size:16px;font-weight:700;color:#0f172a;">Selamat datang, <span style="color:#2563eb;">{{ Auth::user()->name ?? 'Admin' }}</span> 👋</p>
|
||||
<p class="adm-greeting-sub" style="font-size:13px;color:#64748b;margin-top:4px;font-weight:500;">Panel admin — kelola dan pantau seluruh data warga.</p>
|
||||
</div>
|
||||
</div>
|
||||
<div style="display:flex;align-items:center;gap:12px;background:#fffbeb;border:1.5px solid #fde68a;border-radius:16px;padding:14px 16px;">
|
||||
<div style="width:36px;height:36px;border-radius:50%;background:#f59e0b;display:flex;align-items:center;justify-content:center;flex-shrink:0;">
|
||||
<svg width="16" height="16" fill="none" stroke="#fff" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 17h5l-1.405-1.405A2.032 2.032 0 0118 14.158V11a6 6 0 00-9.33-4.976A6 6 0 006 11v3.159c0 .538-.214 1.055-.595 1.436L4 17h5m6 0v1a3 3 0 11-6 0v-1m6 0H9"/></svg>
|
||||
<div style="display:flex;align-items:center;gap:14px;background:#fffbeb;border:1.5px solid #fde68a;border-radius:16px;padding:16px 20px;">
|
||||
<div style="width:44px;height:44px;border-radius:50%;background:#f59e0b;display:flex;align-items:center;justify-content:center;flex-shrink:0;">
|
||||
<svg width="20" height="20" fill="none" stroke="#fff" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 17h5l-1.405-1.405A2.032 2.032 0 0118 14.158V11a6 6 0 00-9.33-4.976A6 6 0 006 11v3.159c0 .538-.214 1.055-.595 1.436L4 17h5m6 0v1a3 3 0 11-6 0v-1m6 0H9"/></svg>
|
||||
</div>
|
||||
<div>
|
||||
<p style="font-size:12px;font-weight:700;color:#92400e;">{{ $totalPending ?? 0 }} data menunggu verifikasi</p>
|
||||
<a href="{{ route('admin.data-warga') }}" style="font-size:11px;color:#b45309;text-decoration:none;font-weight:600;">Proses sekarang →</a>
|
||||
<p class="adm-pending-txt" style="font-size:15px;font-weight:700;color:#92400e;">{{ $totalPending ?? 0 }} data menunggu verifikasi</p>
|
||||
<a href="{{ route('admin.data-warga') }}" class="adm-pending-link" style="font-size:13px;color:#b45309;text-decoration:none;font-weight:600;">Proses sekarang →</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{-- STAT CARDS --}}
|
||||
<div class="adm-stat-grid" style="display:grid;grid-template-columns:repeat(4,1fr);gap:12px;">
|
||||
<div class="adm-stat-grid" style="display:grid;grid-template-columns:repeat(4,1fr);gap:14px;">
|
||||
|
||||
@php
|
||||
$cards = [
|
||||
|
|
@ -179,15 +259,15 @@ function clearNotifs(){NS.clearAll();renderNotifs();}
|
|||
@endphp
|
||||
|
||||
@foreach($cards as $c)
|
||||
<div style="position:relative;background:linear-gradient(135deg,{{ $c['grad'] }});color:#fff;border-radius:16px;padding:16px;display:flex;align-items:center;justify-content:space-between;overflow:hidden;box-shadow:0 6px 20px {{ $c['shadow'] }};">
|
||||
<div style="position:relative;background:linear-gradient(135deg,{{ $c['grad'] }});color:#fff;border-radius:18px;padding:20px 18px;display:flex;align-items:center;justify-content:space-between;overflow:hidden;box-shadow:0 6px 20px {{ $c['shadow'] }};">
|
||||
<div class="sc-blob1"></div><div class="sc-blob2"></div>
|
||||
<div style="position:relative;z-index:1;">
|
||||
<p style="font-size:10px;font-weight:600;color:{{ $c['tc'] }};">{{ $c['label'] }}</p>
|
||||
<p style="font-size:28px;font-weight:900;margin-top:2px;line-height:1;letter-spacing:-.04em;">{{ $c['val'] }}</p>
|
||||
<p style="font-size:10px;color:rgba(255,255,255,.6);margin-top:2px;">{{ $c['sub'] }}</p>
|
||||
<p class="adm-stat-card-lbl" style="font-size:13px;font-weight:700;color:{{ $c['tc'] }};">{{ $c['label'] }}</p>
|
||||
<p class="adm-stat-card-num" style="font-size:34px;font-weight:900;margin-top:4px;line-height:1;letter-spacing:-.04em;">{{ $c['val'] }}</p>
|
||||
<p class="adm-stat-card-sub" style="font-size:12px;color:rgba(255,255,255,.7);margin-top:4px;">{{ $c['sub'] }}</p>
|
||||
</div>
|
||||
<div style="position:relative;z-index:1;width:40px;height:40px;background:rgba(255,255,255,.18);border-radius:12px;display:flex;align-items:center;justify-content:center;flex-shrink:0;border:1px solid rgba(255,255,255,.2);">
|
||||
<svg width="20" height="20" fill="none" stroke="#fff" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="{{ $c['icon'] }}"/></svg>
|
||||
<div class="adm-stat-card-icon" style="position:relative;z-index:1;width:50px;height:50px;background:rgba(255,255,255,.18);border-radius:14px;display:flex;align-items:center;justify-content:center;flex-shrink:0;border:1px solid rgba(255,255,255,.2);">
|
||||
<svg width="26" height="26" fill="none" stroke="#fff" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="{{ $c['icon'] }}"/></svg>
|
||||
</div>
|
||||
</div>
|
||||
@endforeach
|
||||
|
|
@ -195,81 +275,158 @@ function clearNotifs(){NS.clearAll();renderNotifs();}
|
|||
</div>
|
||||
|
||||
{{-- GRAFIK + DATA PER DUSUN --}}
|
||||
<div class="adm-chart-grid" style="display:grid;grid-template-columns:2fr 1fr;gap:14px;">
|
||||
<div class="adm-chart-grid" style="display:grid;grid-template-columns:2fr 1fr;gap:16px;">
|
||||
|
||||
{{-- Grafik --}}
|
||||
<div class="db-card">
|
||||
<div style="padding:12px 16px;border-bottom:1px solid #f1f5f9;background:#fafbfc;display:flex;align-items:center;justify-content:space-between;flex-wrap:wrap;gap:6px;">
|
||||
<div style="display:flex;align-items:center;gap:7px;">
|
||||
<div style="width:3px;height:15px;background:linear-gradient(180deg,#3b82f6,#2563eb);border-radius:4px;"></div>
|
||||
<h3 style="font-size:13px;font-weight:700;color:#1e293b;">Warga per Dusun</h3>
|
||||
{{-- Grafik Modern --}}
|
||||
<div class="db-card" style="position:relative;">
|
||||
|
||||
{{-- Header --}}
|
||||
<div style="padding:16px 20px 14px;display:flex;align-items:center;justify-content:space-between;flex-wrap:wrap;gap:8px;">
|
||||
<div>
|
||||
<div style="display:flex;align-items:center;gap:8px;">
|
||||
<div style="width:10px;height:10px;border-radius:50%;background:linear-gradient(135deg,#60a5fa,#2563eb);box-shadow:0 0 0 3px rgba(37,99,235,.15);flex-shrink:0;"></div>
|
||||
<h3 class="adm-section-title" style="font-size:15px;font-weight:800;color:#0f172a;">Warga per Dusun</h3>
|
||||
</div>
|
||||
<p style="font-size:12px;color:#94a3b8;margin-top:3px;margin-left:18px;">Distribusi warga di setiap dusun</p>
|
||||
</div>
|
||||
<div style="text-align:right;background:#eff6ff;border:1px solid #bfdbfe;border-radius:12px;padding:8px 14px;">
|
||||
<p style="font-size:20px;font-weight:900;color:#1d4ed8;line-height:1;">{{ $wargaPerDusun->sum('total') }}</p>
|
||||
<p style="font-size:11px;color:#60a5fa;font-weight:600;">total warga</p>
|
||||
</div>
|
||||
<span style="font-size:10.5px;color:#94a3b8;background:#fff;border:1px solid #f1f5f9;padding:2px 9px;border-radius:20px;font-weight:600;">
|
||||
{{ $wargaPerDusun->sum('total') }} total
|
||||
</span>
|
||||
</div>
|
||||
<div style="padding:16px;overflow-x:auto;">
|
||||
|
||||
{{-- Divider --}}
|
||||
<div style="height:1px;background:linear-gradient(90deg,transparent,#e2e8f0 20%,#e2e8f0 80%,transparent);margin:0 16px;"></div>
|
||||
|
||||
{{-- SVG Chart --}}
|
||||
<div style="padding:16px 12px 4px;position:relative;">
|
||||
@php
|
||||
$dusuns=$wargaPerDusun->values();
|
||||
$n=max($dusuns->count(),1);
|
||||
$maxV=max((int)$dusuns->max('total'),1);
|
||||
$W=560;$H=200;$pL=40;$pR=20;$pT=28;$pB=44;
|
||||
$cW=$W-$pL-$pR;$cH=$H-$pT-$pB;
|
||||
$step=max(1,(int)ceil($maxV/4));
|
||||
$yTicks=range(0,$maxV+$step,$step);
|
||||
$dusuns = $wargaPerDusun->values();
|
||||
$n = max($dusuns->count(), 1);
|
||||
$maxV = max((int)$dusuns->max('total'), 1);
|
||||
|
||||
$W=360; $H=190; $pL=36; $pR=12; $pT=30; $pB=46;
|
||||
$cW=$W-$pL-$pR; $cH=$H-$pT-$pB;
|
||||
$botY=$pT+$cH;
|
||||
|
||||
$pts=[];
|
||||
foreach($dusuns as $i=>$d){
|
||||
$x=$pL+($n===1?$cW/2:($i/($n-1))*$cW);
|
||||
$y=$pT+$cH-($d->total/$maxV)*$cH;
|
||||
$pts[]=['x'=>round($x,1),'y'=>round($y,1),'val'=>$d->total,'name'=>$d->dusun];
|
||||
$x = round($pL + ($n===1 ? $cW/2 : ($i/($n-1))*$cW), 1);
|
||||
$y = round($pT + $cH - ($d->total / $maxV) * $cH, 1);
|
||||
$pts[] = ['x'=>$x,'y'=>$y,'val'=>$d->total,'name'=>$d->dusun];
|
||||
}
|
||||
$poly=implode(' ',array_map(fn($p)=>"{$p['x']},{$p['y']}",$pts));
|
||||
$firstPt=$pts[0];$lastPt=$pts[count($pts)-1];
|
||||
$botY=$pT+$cH;
|
||||
$areaPath="M {$firstPt['x']},{$firstPt['y']} ".implode(' ',array_map(fn($p)=>"L {$p['x']},{$p['y']}",array_slice($pts,1)))." L {$lastPt['x']},{$botY} L {$firstPt['x']},{$botY} Z";
|
||||
|
||||
/* Smooth cubic bezier */
|
||||
$linePath = "M {$pts[0]['x']},{$pts[0]['y']}";
|
||||
for($i=0;$i<count($pts)-1;$i++){
|
||||
$cp1x = round($pts[$i]['x'] + ($pts[$i+1]['x']-$pts[$i]['x'])*0.45, 1);
|
||||
$cp2x = round($pts[$i+1]['x'] - ($pts[$i+1]['x']-$pts[$i]['x'])*0.45, 1);
|
||||
$linePath .= " C {$cp1x},{$pts[$i]['y']} {$cp2x},{$pts[$i+1]['y']} {$pts[$i+1]['x']},{$pts[$i+1]['y']}";
|
||||
}
|
||||
$areaPath = $linePath." L {$pts[count($pts)-1]['x']},{$botY} L {$pts[0]['x']},{$botY} Z";
|
||||
|
||||
$step = max(1,(int)ceil($maxV/4));
|
||||
$yTicks = range(0, $maxV, $step);
|
||||
@endphp
|
||||
<svg viewBox="0 0 {{ $W }} {{ $H }}" width="100%" style="min-width:260px;" xmlns="http://www.w3.org/2000/svg">
|
||||
|
||||
<svg viewBox="0 0 {{ $W }} {{ $H }}" width="100%" style="display:block;overflow:visible;" xmlns="http://www.w3.org/2000/svg">
|
||||
<defs>
|
||||
<linearGradient id="fg" x1="0" y1="0" x2="0" y2="1">
|
||||
<stop offset="0%" stop-color="#3b82f6" stop-opacity=".18"/>
|
||||
<linearGradient id="areaGrad" x1="0" y1="0" x2="0" y2="1">
|
||||
<stop offset="0%" stop-color="#3b82f6" stop-opacity=".25"/>
|
||||
<stop offset="75%" stop-color="#3b82f6" stop-opacity=".05"/>
|
||||
<stop offset="100%" stop-color="#3b82f6" stop-opacity="0"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="lineGrad" x1="0" y1="0" x2="1" y2="0">
|
||||
<stop offset="0%" stop-color="#60a5fa"/>
|
||||
<stop offset="100%" stop-color="#1d4ed8"/>
|
||||
</linearGradient>
|
||||
<filter id="glow">
|
||||
<feGaussianBlur stdDeviation="2" result="b"/>
|
||||
<feMerge><feMergeNode in="b"/><feMergeNode in="SourceGraphic"/></feMerge>
|
||||
</filter>
|
||||
<clipPath id="cClip">
|
||||
<rect x="{{ $pL }}" y="{{ $pT-2 }}" width="{{ $cW }}" height="{{ $cH+4 }}"/>
|
||||
</clipPath>
|
||||
</defs>
|
||||
|
||||
{{-- Grid garis datar dashed --}}
|
||||
@foreach($yTicks as $t)
|
||||
@if($t<=$maxV+$step)
|
||||
@php $gy=$pT+$cH-min($t,$maxV)/$maxV*$cH; @endphp
|
||||
<line x1="{{ $pL }}" y1="{{ $gy }}" x2="{{ $W-$pR }}" y2="{{ $gy }}" stroke="#f1f5f9" stroke-width="1"/>
|
||||
<text x="{{ $pL-5 }}" y="{{ $gy+4 }}" text-anchor="end" font-size="10" fill="#94a3b8">{{ $t }}</text>
|
||||
@endif
|
||||
@php $gy = round($pT + $cH - ($t/$maxV)*$cH, 1); @endphp
|
||||
<line x1="{{ $pL }}" y1="{{ $gy }}" x2="{{ $W-$pR }}" y2="{{ $gy }}"
|
||||
stroke="#f1f5f9" stroke-width="1" stroke-dasharray="4,5"/>
|
||||
<text x="{{ $pL-5 }}" y="{{ $gy+4 }}" text-anchor="end"
|
||||
font-size="10" fill="#cbd5e1" font-weight="700"
|
||||
font-family="Figtree,sans-serif">{{ $t }}</text>
|
||||
@endforeach
|
||||
<line x1="{{ $pL }}" y1="{{ $botY }}" x2="{{ $W-$pR }}" y2="{{ $botY }}" stroke="#cbd5e1" stroke-width="1.5"/>
|
||||
<line x1="{{ $pL }}" y1="{{ $pT }}" x2="{{ $pL }}" y2="{{ $botY }}" stroke="#cbd5e1" stroke-width="1.5"/>
|
||||
<path d="{{ $areaPath }}" fill="url(#fg)"/>
|
||||
<polyline points="{{ $poly }}" fill="none" stroke="#2563eb" stroke-width="2.5" stroke-linejoin="round" stroke-linecap="round"/>
|
||||
@foreach($pts as $p)
|
||||
<circle cx="{{ $p['x'] }}" cy="{{ $p['y'] }}" r="5" fill="white" stroke="#2563eb" stroke-width="2.5"/>
|
||||
<text x="{{ $p['x'] }}" y="{{ $p['y']-10 }}" text-anchor="middle" font-size="11" font-weight="700" fill="#2563eb">{{ $p['val'] }}</text>
|
||||
<text x="{{ $p['x'] }}" y="{{ $botY+15 }}" text-anchor="middle" font-size="10" fill="#64748b">{{ Str::limit($p['name'],8) }}</text>
|
||||
|
||||
{{-- Baseline --}}
|
||||
<line x1="{{ $pL }}" y1="{{ $botY }}" x2="{{ $W-$pR }}" y2="{{ $botY }}"
|
||||
stroke="#e2e8f0" stroke-width="1.2"/>
|
||||
|
||||
{{-- Area fill --}}
|
||||
<path d="{{ $areaPath }}" fill="url(#areaGrad)" clip-path="url(#cClip)"/>
|
||||
|
||||
{{-- Line smooth + glow --}}
|
||||
{{-- Shadow line tipis di bawah --}}
|
||||
<path d="{{ $linePath }}" fill="none" stroke="#bfdbfe"
|
||||
stroke-width="5" stroke-linecap="round" opacity="0.4" clip-path="url(#cClip)"/>
|
||||
{{-- Garis utama --}}
|
||||
<path d="{{ $linePath }}" fill="none" stroke="url(#lineGrad)"
|
||||
stroke-width="3" stroke-linecap="round" clip-path="url(#cClip)"/>
|
||||
|
||||
{{-- Titik data --}}
|
||||
@foreach($pts as $idx => $p)
|
||||
{{-- Ring luar --}}
|
||||
<circle cx="{{ $p['x'] }}" cy="{{ $p['y'] }}" r="10"
|
||||
fill="rgba(37,99,235,.08)"/>
|
||||
{{-- Dot putih dengan border biru --}}
|
||||
<circle cx="{{ $p['x'] }}" cy="{{ $p['y'] }}" r="5.5"
|
||||
fill="#fff" stroke="url(#lineGrad)" stroke-width="2.5"
|
||||
filter="url(#glow)"/>
|
||||
{{-- Dot inner biru kecil --}}
|
||||
<circle cx="{{ $p['x'] }}" cy="{{ $p['y'] }}" r="2.5"
|
||||
fill="#2563eb"/>
|
||||
|
||||
{{-- Badge nilai di atas titik --}}
|
||||
<rect x="{{ $p['x']-12 }}" y="{{ $p['y']-28 }}"
|
||||
width="24" height="16" rx="5"
|
||||
fill="#1d4ed8"/>
|
||||
<text x="{{ $p['x'] }}" y="{{ $p['y']-17 }}" text-anchor="middle"
|
||||
font-size="10" font-weight="800" fill="#fff"
|
||||
font-family="Figtree,sans-serif">{{ $p['val'] }}</text>
|
||||
|
||||
{{-- Label dusun di bawah --}}
|
||||
<text x="{{ $p['x'] }}" y="{{ $botY+14 }}" text-anchor="middle"
|
||||
font-size="10" fill="#64748b" font-weight="700"
|
||||
font-family="Figtree,sans-serif">{{ Str::limit($p['name'],7) }}</text>
|
||||
<line x1="{{ $p['x'] }}" y1="{{ $botY+2 }}" x2="{{ $p['x'] }}" y2="{{ $botY+5 }}"
|
||||
stroke="#e2e8f0" stroke-width="1"/>
|
||||
@endforeach
|
||||
</svg>
|
||||
</div>
|
||||
|
||||
{{-- Legend --}}
|
||||
<div style="padding:4px 20px 16px;display:flex;align-items:center;gap:8px;">
|
||||
<div style="width:20px;height:3px;background:linear-gradient(90deg,#60a5fa,#1d4ed8);border-radius:99px;"></div>
|
||||
<span style="font-size:12px;color:#94a3b8;font-weight:600;">Jumlah warga per dusun</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{-- Data per dusun --}}
|
||||
<div class="db-card">
|
||||
<div style="padding:12px 16px;border-bottom:1px solid #f1f5f9;background:#fafbfc;display:flex;align-items:center;gap:7px;">
|
||||
<div style="width:3px;height:15px;background:linear-gradient(180deg,#10b981,#059669);border-radius:4px;"></div>
|
||||
<h3 style="font-size:13px;font-weight:700;color:#1e293b;">Per Dusun</h3>
|
||||
<div style="padding:14px 18px;border-bottom:1px solid #f1f5f9;background:#fafbfc;display:flex;align-items:center;gap:9px;">
|
||||
<div style="width:4px;height:18px;background:linear-gradient(180deg,#10b981,#059669);border-radius:4px;"></div>
|
||||
<h3 class="adm-section-title" style="font-size:15px;font-weight:700;color:#1e293b;">Per Dusun</h3>
|
||||
</div>
|
||||
<div style="padding:12px;display:flex;flex-direction:column;gap:8px;">
|
||||
<div style="padding:14px;display:flex;flex-direction:column;gap:10px;">
|
||||
@php $maxDusun=$wargaPerDusun->max('total')?:1; @endphp
|
||||
@foreach($wargaPerDusun as $data)
|
||||
<div style="padding:10px 12px;background:#f8fafc;border:1.5px solid #f1f5f9;border-radius:12px;">
|
||||
<div style="display:flex;align-items:center;justify-content:space-between;margin-bottom:6px;">
|
||||
<span style="font-size:12px;font-weight:700;color:#1e293b;">{{ $data->dusun }}</span>
|
||||
<span style="font-size:14px;font-weight:900;color:#2563eb;">{{ $data->total }}</span>
|
||||
<div style="padding:12px 14px;background:#f8fafc;border:1.5px solid #f1f5f9;border-radius:12px;">
|
||||
<div style="display:flex;align-items:center;justify-content:space-between;margin-bottom:8px;">
|
||||
<span class="adm-dusun-name" style="font-size:14px;font-weight:700;color:#1e293b;">{{ $data->dusun }}</span>
|
||||
<span class="adm-dusun-num" style="font-size:17px;font-weight:900;color:#2563eb;">{{ $data->total }}</span>
|
||||
</div>
|
||||
<div style="width:100%;height:5px;background:#e2e8f0;border-radius:99px;overflow:hidden;">
|
||||
<div class="adm-dusun-bar" style="width:100%;height:7px;background:#e2e8f0;border-radius:99px;overflow:hidden;">
|
||||
<div style="height:100%;background:linear-gradient(90deg,#3b82f6,#2563eb);border-radius:99px;width:{{ ($data->total/$maxDusun)*100 }}%;"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -281,18 +438,18 @@ function clearNotifs(){NS.clearAll();renderNotifs();}
|
|||
|
||||
{{-- TOP 10 --}}
|
||||
<div class="db-card tt">
|
||||
<div style="padding:12px 16px;border-bottom:1.5px solid #f1f5f9;background:#fafbfc;display:flex;align-items:center;justify-content:space-between;flex-wrap:wrap;gap:8px;">
|
||||
<div style="display:flex;align-items:center;gap:8px;">
|
||||
<div style="width:3px;height:16px;background:linear-gradient(180deg,#3b82f6,#2563eb);border-radius:4px;"></div>
|
||||
<div style="padding:14px 18px;border-bottom:1.5px solid #f1f5f9;background:#fafbfc;display:flex;align-items:center;justify-content:space-between;flex-wrap:wrap;gap:10px;">
|
||||
<div style="display:flex;align-items:center;gap:10px;">
|
||||
<div style="width:4px;height:18px;background:linear-gradient(180deg,#3b82f6,#2563eb);border-radius:4px;"></div>
|
||||
<div>
|
||||
<h3 style="font-size:13px;font-weight:800;color:#0f172a;">Top 10 Probabilitas Tertinggi</h3>
|
||||
<p style="font-size:10px;color:#94a3b8;margin-top:1px;">Diurutkan dari probabilitas terbesar</p>
|
||||
<h3 class="adm-section-title" style="font-size:15px;font-weight:800;color:#0f172a;">Top 10 Probabilitas Tertinggi</h3>
|
||||
<p style="font-size:12px;color:#94a3b8;margin-top:3px;">Diurutkan dari probabilitas terbesar</p>
|
||||
</div>
|
||||
</div>
|
||||
<a href="{{ route('admin.filterisasi') }}"
|
||||
style="display:inline-flex;align-items:center;gap:5px;font-size:11.5px;font-weight:700;color:#2563eb;background:#eff6ff;border:1px solid #bfdbfe;padding:5px 12px;border-radius:9px;text-decoration:none;">
|
||||
<a href="{{ route('admin.filterisasi') }}" class="adm-lihat-semua"
|
||||
style="display:inline-flex;align-items:center;gap:6px;font-size:14px;font-weight:700;color:#2563eb;background:#eff6ff;border:1px solid #bfdbfe;padding:10px 18px;border-radius:10px;text-decoration:none;min-height:44px;">
|
||||
Lihat Semua
|
||||
<svg width="11" height="11" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5l7 7-7 7"/></svg>
|
||||
<svg width="13" height="13" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5l7 7-7 7"/></svg>
|
||||
</a>
|
||||
</div>
|
||||
|
||||
|
|
@ -305,7 +462,7 @@ function clearNotifs(){NS.clearAll();renderNotifs();}
|
|||
<th>NIK</th>
|
||||
<th>Dusun / RT</th>
|
||||
<th>Penghasilan</th>
|
||||
<th style="min-width:140px;">Probabilitas</th>
|
||||
<th style="min-width:160px;">Probabilitas</th>
|
||||
<th>Status</th>
|
||||
</tr>
|
||||
</thead>
|
||||
|
|
@ -318,34 +475,34 @@ function clearNotifs(){NS.clearAll();renderNotifs();}
|
|||
$bg = $index < 3 ? $rk[$index] : '#f1f5f9';
|
||||
$tc = $index < 3 ? '#fff' : '#64748b';
|
||||
@endphp
|
||||
<div style="width:24px;height:24px;border-radius:7px;background:{{ $bg }};display:flex;align-items:center;justify-content:center;font-size:11px;font-weight:800;color:{{ $tc }};">{{ $index+1 }}</div>
|
||||
<div class="adm-rank-badge" style="width:30px;height:30px;border-radius:8px;background:{{ $bg }};display:flex;align-items:center;justify-content:center;font-size:13px;font-weight:800;color:{{ $tc }};">{{ $index+1 }}</div>
|
||||
</td>
|
||||
<td>
|
||||
<div style="display:flex;align-items:center;gap:8px;">
|
||||
<div style="width:28px;height:28px;border-radius:8px;background:linear-gradient(135deg,#3b82f6,#2563eb);display:flex;align-items:center;justify-content:center;flex-shrink:0;font-size:11px;font-weight:800;color:#fff;">
|
||||
<div style="display:flex;align-items:center;gap:10px;">
|
||||
<div class="adm-avatar-sm" style="width:34px;height:34px;border-radius:10px;background:linear-gradient(135deg,#3b82f6,#2563eb);display:flex;align-items:center;justify-content:center;flex-shrink:0;font-size:14px;font-weight:800;color:#fff;">
|
||||
{{ strtoupper(substr($warga->nama,0,1)) }}
|
||||
</div>
|
||||
<div>
|
||||
<div style="font-size:12px;font-weight:700;color:#0f172a;white-space:nowrap;">{{ $warga->nama }}</div>
|
||||
<div style="font-size:10px;color:#94a3b8;">{{ $warga->pekerjaan }}</div>
|
||||
<div class="adm-tbl-name" style="font-size:14px;font-weight:700;color:#0f172a;white-space:nowrap;">{{ $warga->nama }}</div>
|
||||
<div class="adm-tbl-job" style="font-size:12px;color:#94a3b8;margin-top:2px;">{{ $warga->pekerjaan }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</td>
|
||||
<td><span style="font-family:monospace;font-size:10.5px;color:#6b7280;background:#f3f4f6;padding:2px 7px;border-radius:6px;white-space:nowrap;">{{ $warga->nik }}</span></td>
|
||||
<td><span class="adm-tbl-nik" style="font-family:monospace;font-size:12px;color:#6b7280;background:#f3f4f6;padding:3px 9px;border-radius:6px;white-space:nowrap;">{{ $warga->nik }}</span></td>
|
||||
<td>
|
||||
<div style="white-space:nowrap;">
|
||||
<span style="display:inline-block;padding:2px 8px;border-radius:6px;background:#eff6ff;color:#1d4ed8;font-size:10.5px;font-weight:700;border:1px solid #bfdbfe;">{{ $warga->dusun }}</span>
|
||||
<span style="font-size:10.5px;color:#64748b;font-weight:600;margin-left:4px;">RT {{ str_pad($warga->rt??'0',3,'0',STR_PAD_LEFT) }}</span>
|
||||
<span class="adm-tbl-dusun" style="display:inline-block;padding:3px 10px;border-radius:6px;background:#eff6ff;color:#1d4ed8;font-size:12px;font-weight:700;border:1px solid #bfdbfe;">{{ $warga->dusun }}</span>
|
||||
<span class="adm-tbl-rt" style="font-size:12px;color:#64748b;font-weight:600;margin-left:5px;">RT {{ str_pad($warga->rt??'0',3,'0',STR_PAD_LEFT) }}</span>
|
||||
</div>
|
||||
</td>
|
||||
<td style="white-space:nowrap;font-weight:600;">Rp {{ number_format($warga->penghasilan,0,',','.') }}</td>
|
||||
<td class="adm-tbl-income" style="white-space:nowrap;font-weight:600;font-size:14px;">Rp {{ number_format($warga->penghasilan,0,',','.') }}</td>
|
||||
<td>
|
||||
@php $sc=$warga->probabilitas>=70?'#10b981':($warga->probabilitas>=40?'#f59e0b':'#f43f5e'); @endphp
|
||||
<div style="display:flex;align-items:center;gap:6px;">
|
||||
<div style="flex:1;background:#f1f5f9;border-radius:99px;height:5px;overflow:hidden;min-width:70px;">
|
||||
<div style="display:flex;align-items:center;gap:8px;">
|
||||
<div style="flex:1;background:#f1f5f9;border-radius:99px;height:7px;overflow:hidden;min-width:80px;">
|
||||
<div style="height:100%;border-radius:99px;background:{{ $sc }};width:{{ min($warga->probabilitas,100) }}%;"></div>
|
||||
</div>
|
||||
<span style="font-size:11.5px;font-weight:800;color:{{ $sc }};white-space:nowrap;">{{ number_format($warga->probabilitas,1) }}%</span>
|
||||
<span class="adm-tbl-pct" style="font-size:13px;font-weight:800;color:{{ $sc }};white-space:nowrap;">{{ number_format($warga->probabilitas,1) }}%</span>
|
||||
</div>
|
||||
</td>
|
||||
<td>
|
||||
|
|
@ -353,13 +510,13 @@ function clearNotifs(){NS.clearAll();renderNotifs();}
|
|||
$st=['pending'=>['#fffbeb','#b45309','#fde68a','Pending'],'diterima'=>['#f0fdf4','#166534','#bbf7d0','Diterima'],'ditolak'=>['#fff1f2','#be123c','#fecdd3','Ditolak']];
|
||||
$s=$st[$warga->status]??['#eff6ff','#1d4ed8','#bfdbfe',ucfirst($warga->status)];
|
||||
@endphp
|
||||
<span style="display:inline-flex;align-items:center;gap:4px;padding:3px 9px;font-size:10.5px;font-weight:700;border-radius:20px;background:{{ $s[0] }};color:{{ $s[1] }};border:1px solid {{ $s[2] }};white-space:nowrap;">
|
||||
<span style="width:5px;height:5px;border-radius:50%;background:{{ $s[1] }};flex-shrink:0;"></span>{{ $s[3] }}
|
||||
<span class="adm-tbl-badge" style="display:inline-flex;align-items:center;gap:5px;padding:5px 12px;font-size:12px;font-weight:700;border-radius:20px;background:{{ $s[0] }};color:{{ $s[1] }};border:1px solid {{ $s[2] }};white-space:nowrap;">
|
||||
<span style="width:6px;height:6px;border-radius:50%;background:{{ $s[1] }};flex-shrink:0;"></span>{{ $s[3] }}
|
||||
</span>
|
||||
</td>
|
||||
</tr>
|
||||
@empty
|
||||
<tr><td colspan="7" style="text-align:center;padding:40px;color:#94a3b8;font-size:13px;">Belum ada data warga</td></tr>
|
||||
<tr><td colspan="7" style="text-align:center;padding:48px;color:#94a3b8;font-size:15px;">Belum ada data warga</td></tr>
|
||||
@endforelse
|
||||
</tbody>
|
||||
</table>
|
||||
|
|
|
|||
|
|
@ -0,0 +1,215 @@
|
|||
<x-app-layout>
|
||||
|
||||
<x-slot name="header">
|
||||
<div style="display:flex;align-items:center;justify-content:space-between;gap:12px;flex-wrap:wrap;">
|
||||
<div>
|
||||
<h2 style="font-size:16px;font-weight:800;color:#0f172a;letter-spacing:-.02em;">Arsip Data Warga</h2>
|
||||
<p style="font-size:11px;color:#94a3b8;margin-top:2px;">Data warga yang telah diarsipkan berdasarkan tahun periode</p>
|
||||
</div>
|
||||
<a href="{{ route('admin.data-warga') }}" style="display:inline-flex;align-items:center;gap:6px;background:#fff;border:1.5px solid #e2e8f0;border-radius:10px;padding:7px 14px;font-size:12px;font-weight:600;color:#374151;text-decoration:none;white-space:nowrap;">
|
||||
<svg width="13" height="13" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 19l-7-7 7-7"/></svg>
|
||||
Kembali ke Data Warga
|
||||
</a>
|
||||
</div>
|
||||
</x-slot>
|
||||
|
||||
<style>
|
||||
.dw-card{background:#fff;border-radius:16px;border:1.5px solid #f1f5f9;box-shadow:0 1px 4px rgba(0,0,0,.04),0 4px 16px rgba(0,0,0,.03);overflow:hidden;}
|
||||
.fl-box{display:flex;align-items:center;gap:9px;border-radius:12px;padding:10px 14px;font-size:12px;font-weight:500;}
|
||||
.sw{position:relative;}
|
||||
.sw svg{position:absolute;left:11px;top:50%;transform:translateY(-50%);width:14px;height:14px;stroke:#9ca3af;pointer-events:none;}
|
||||
.sw input{width:100%;padding:9px 12px 9px 34px;font-size:13px;color:#111827;background:#f9fafb;border:1.5px solid #e5e7eb;border-radius:11px;outline:none;transition:border-color .15s,box-shadow .15s;font-family:inherit;}
|
||||
.sw input:focus{border-color:#3b82f6;background:#fff;box-shadow:0 0 0 3px rgba(59,130,246,.10);}
|
||||
.sw input::placeholder{color:#9ca3af;}
|
||||
.tbl-wrap{overflow-x:auto;-webkit-overflow-scrolling:touch;}
|
||||
table{width:100%;border-collapse:collapse;min-width:700px;}
|
||||
thead tr{background:#f8fafc;border-bottom:1.5px solid #f1f5f9;}
|
||||
thead th{padding:9px 12px;text-align:left;font-size:9.5px;font-weight:700;color:#94a3b8;text-transform:uppercase;letter-spacing:.07em;white-space:nowrap;}
|
||||
tbody tr{border-bottom:1px solid #f8fafc;transition:background .1s;}
|
||||
tbody tr:hover{background:#f0f7ff;}
|
||||
tbody tr:last-child{border-bottom:none;}
|
||||
tbody td{padding:10px 12px;vertical-align:middle;}
|
||||
.av{width:30px;height:30px;border-radius:9px;background:linear-gradient(135deg,#64748b,#475569);display:flex;align-items:center;justify-content:center;font-size:11px;font-weight:800;color:#fff;flex-shrink:0;}
|
||||
.nik-pill{font-family:monospace;font-size:10px;color:#6b7280;background:#f3f4f6;padding:2px 6px;border-radius:6px;white-space:nowrap;}
|
||||
.rt-badge{display:inline-flex;align-items:center;padding:2px 7px;border-radius:6px;background:#eff6ff;color:#1d4ed8;font-size:10px;font-weight:700;border:1px solid #bfdbfe;margin-top:2px;}
|
||||
.badge{display:inline-flex;align-items:center;gap:4px;padding:3px 9px;border-radius:20px;font-size:10.5px;font-weight:700;white-space:nowrap;}
|
||||
.dot{width:5px;height:5px;border-radius:50%;flex-shrink:0;}
|
||||
.stat-yes{background:#f0fdf4;color:#166534;border:1px solid #bbf7d0;}
|
||||
.stat-no{background:#fff1f2;color:#9f1239;border:1px solid #fecdd3;}
|
||||
.stat-pending{background:#fffbeb;color:#b45309;border:1px solid #fde68a;}
|
||||
.prob-wrap{display:flex;align-items:center;gap:6px;}
|
||||
.prob-bg{flex:1;height:5px;background:#f1f5f9;border-radius:99px;overflow:hidden;min-width:50px;}
|
||||
.prob-bar{height:100%;border-radius:99px;}
|
||||
.tab-tahun{display:inline-flex;align-items:center;padding:6px 14px;border-radius:20px;font-size:12px;font-weight:600;text-decoration:none;border:1.5px solid #e2e8f0;color:#64748b;background:#fff;transition:all .15s;white-space:nowrap;}
|
||||
.tab-tahun:hover{background:#f1f5f9;}
|
||||
.tab-tahun.active{background:#1e40af;color:#fff;border-color:#1e40af;}
|
||||
.btn-act{display:inline-flex;align-items:center;gap:4px;padding:4px 9px;border-radius:8px;font-size:10.5px;font-weight:600;text-decoration:none;border:none;cursor:pointer;white-space:nowrap;line-height:1.4;transition:filter .1s;font-family:inherit;}
|
||||
.btn-act:hover{filter:brightness(.92);}
|
||||
.btn-act svg{width:11px;height:11px;flex-shrink:0;}
|
||||
</style>
|
||||
|
||||
@if(session('success'))
|
||||
<div class="fl-box" style="background:#f0fdf4;border:1.5px solid #bbf7d0;color:#166534;margin-bottom:10px;">
|
||||
<svg width="14" height="14" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z"/></svg>
|
||||
{{ session('success') }}
|
||||
</div>
|
||||
@endif
|
||||
@if(session('error'))
|
||||
<div class="fl-box" style="background:#fff1f2;border:1.5px solid #fecdd3;color:#9f1239;margin-bottom:10px;">
|
||||
<svg width="14" height="14" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 9v2m0 4h.01M12 3a9 9 0 100 18A9 9 0 0012 3z"/></svg>
|
||||
{{ session('error') }}
|
||||
</div>
|
||||
@endif
|
||||
|
||||
{{-- FILTER TAHUN --}}
|
||||
<div class="dw-card" style="padding:14px 16px;margin-bottom:12px;">
|
||||
<div style="display:flex;align-items:center;gap:7px;margin-bottom:12px;">
|
||||
<div style="width:3px;height:14px;background:#475569;border-radius:4px;"></div>
|
||||
<h3 style="font-size:12px;font-weight:700;color:#374151;">Filter Tahun</h3>
|
||||
</div>
|
||||
|
||||
@if($daftarTahun->isEmpty())
|
||||
<p style="font-size:13px;color:#9ca3af;">Belum ada data yang diarsipkan.</p>
|
||||
@else
|
||||
<div style="display:flex;gap:8px;flex-wrap:wrap;">
|
||||
@foreach($daftarTahun as $th)
|
||||
<a href="{{ route('admin.data-warga.arsip', ['tahun' => $th, 'q' => request('q')]) }}"
|
||||
class="tab-tahun {{ $tahun == $th ? 'active' : '' }}">
|
||||
{{ $th }}
|
||||
</a>
|
||||
@endforeach
|
||||
</div>
|
||||
@endif
|
||||
|
||||
{{-- Search --}}
|
||||
<form method="GET" action="{{ route('admin.data-warga.arsip') }}" style="display:flex;gap:8px;align-items:center;flex-wrap:wrap;margin-top:12px;padding-top:12px;border-top:1px solid #f1f5f9;">
|
||||
<input type="hidden" name="tahun" value="{{ $tahun }}">
|
||||
<div class="sw" style="flex:1;min-width:200px;">
|
||||
<svg fill="none" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z"/></svg>
|
||||
<input type="text" name="q" value="{{ request('q') }}" placeholder="Cari NIK atau Nama...">
|
||||
</div>
|
||||
<button type="submit" style="display:inline-flex;align-items:center;gap:6px;padding:9px 16px;background:#475569;color:#fff;font-size:12px;font-weight:700;border:none;border-radius:11px;cursor:pointer;white-space:nowrap;font-family:inherit;">
|
||||
<svg width="13" height="13" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z"/></svg>
|
||||
Cari
|
||||
</button>
|
||||
@if(request()->filled('q'))
|
||||
<a href="{{ route('admin.data-warga.arsip', ['tahun' => $tahun]) }}" style="display:inline-flex;align-items:center;justify-content:center;width:36px;height:36px;background:#f3f4f6;border-radius:10px;color:#6b7280;text-decoration:none;">
|
||||
<svg width="14" height="14" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12"/></svg>
|
||||
</a>
|
||||
@endif
|
||||
</form>
|
||||
</div>
|
||||
|
||||
{{-- TABLE ARSIP --}}
|
||||
<div class="dw-card">
|
||||
<div style="padding:10px 16px;border-bottom:1px solid #f1f5f9;background:#fafafa;display:flex;align-items:center;justify-content:space-between;flex-wrap:wrap;gap:6px;">
|
||||
<div style="display:flex;align-items:center;gap:7px;">
|
||||
<div style="width:3px;height:15px;background:#475569;border-radius:4px;"></div>
|
||||
<span style="font-size:12.5px;font-weight:700;color:#374151;">
|
||||
Arsip Tahun <span style="color:#1e40af;">{{ $tahun }}</span>
|
||||
@if(isset($arsips) && method_exists($arsips,'total'))
|
||||
— <span style="color:#475569;">{{ $arsips->total() }}</span><span style="color:#9ca3af;font-weight:400;"> data</span>
|
||||
@endif
|
||||
</span>
|
||||
</div>
|
||||
@if(isset($arsips) && method_exists($arsips,'currentPage') && $arsips->lastPage() > 1)
|
||||
<span style="font-size:11px;color:#9ca3af;">Hal. {{ $arsips->currentPage() }} / {{ $arsips->lastPage() }}</span>
|
||||
@endif
|
||||
</div>
|
||||
|
||||
<div class="tbl-wrap">
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>NIK</th>
|
||||
<th>Nama / Pekerjaan</th>
|
||||
<th>Dusun · RT</th>
|
||||
<th>Penghasilan</th>
|
||||
<th>Probabilitas</th>
|
||||
<th>Hasil</th>
|
||||
<th style="text-align:right;">Aksi</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@forelse($arsips ?? [] as $warga)
|
||||
@php
|
||||
$prob = optional($warga->prediksiKelayakan)->probability ?? null;
|
||||
$probPct = $prob !== null ? ($prob <= 1 ? $prob * 100 : $prob) : null;
|
||||
$sc = $probPct !== null ? ($probPct >= 70 ? '#10b981' : ($probPct >= 40 ? '#f59e0b' : '#f43f5e')) : '#e5e7eb';
|
||||
@endphp
|
||||
<tr>
|
||||
<td><span class="nik-pill">{{ $warga->nik }}</span></td>
|
||||
<td>
|
||||
<div style="display:flex;align-items:center;gap:8px;">
|
||||
<div class="av">{{ strtoupper(substr($warga->nama_lengkap,0,1)) }}</div>
|
||||
<div style="min-width:0;">
|
||||
<div style="font-size:12px;font-weight:600;color:#111827;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;max-width:140px;">{{ $warga->nama_lengkap }}</div>
|
||||
<div style="font-size:10.5px;color:#9ca3af;margin-top:1px;">{{ $warga->pekerjaan ?? '-' }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</td>
|
||||
<td>
|
||||
<div style="font-size:11.5px;font-weight:600;color:#374151;white-space:nowrap;">{{ $warga->rt->dusun->nama_dusun ?? '-' }}</div>
|
||||
<span class="rt-badge">RT {{ str_pad($warga->rt->nomor_rt ?? '-', 3, '0', STR_PAD_LEFT) }}</span>
|
||||
</td>
|
||||
<td style="font-size:12px;font-weight:600;color:#374151;white-space:nowrap;">
|
||||
Rp {{ number_format($warga->penghasilan ?? 0, 0, ',', '.') }}
|
||||
</td>
|
||||
<td>
|
||||
@if($probPct !== null)
|
||||
<div class="prob-wrap">
|
||||
<div class="prob-bg"><div class="prob-bar" style="width:{{ min($probPct,100) }}%;background:{{ $sc }};"></div></div>
|
||||
<span style="font-size:11px;font-weight:700;color:{{ $sc }};white-space:nowrap;">{{ number_format($probPct,1) }}%</span>
|
||||
</div>
|
||||
@else
|
||||
<span style="font-size:12px;color:#d1d5db;">—</span>
|
||||
@endif
|
||||
</td>
|
||||
<td>
|
||||
@if(($warga->status_verifikasi ?? '') === 'disetujui')
|
||||
<span class="badge stat-yes"><span class="dot" style="background:#22c55e;"></span>Diterima</span>
|
||||
@elseif(($warga->status_verifikasi ?? '') === 'ditolak')
|
||||
<span class="badge stat-no"><span class="dot" style="background:#f43f5e;"></span>Ditolak</span>
|
||||
@else
|
||||
<span class="badge stat-pending"><span class="dot" style="background:#f59e0b;"></span>Pending</span>
|
||||
@endif
|
||||
</td>
|
||||
<td>
|
||||
<div style="display:flex;align-items:center;justify-content:flex-end;gap:4px;">
|
||||
{{-- Aktifkan Kembali dari Arsip --}}
|
||||
<form method="POST" action="{{ route('admin.data-warga.aktifkan-kembali', $warga->id) }}" onsubmit="return confirm('Keluarkan {{ addslashes($warga->nama_lengkap) }} dari arsip dan aktifkan kembali?')">
|
||||
@csrf
|
||||
<button type="submit" class="btn-act" style="background:#fffbeb;color:#b45309;">
|
||||
<svg fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 4v5h.582m15.356 2A8.001 8.001 0 004.582 9m0 0H9m11 11v-5h-.581m0 0a8.003 8.003 0 01-15.357-2m15.357 2H15"/></svg>
|
||||
Aktifkan Kembali
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
@empty
|
||||
<tr><td colspan="7">
|
||||
<div style="padding:48px 16px;text-align:center;">
|
||||
<div style="width:44px;height:44px;background:#f3f4f6;border-radius:50%;display:flex;align-items:center;justify-content:center;margin:0 auto 10px;">
|
||||
<svg width="20" height="20" fill="none" stroke="#d1d5db" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" d="M5 8h14M5 8a2 2 0 110-4h14a2 2 0 110 4M5 8v10a2 2 0 002 2h10a2 2 0 002-2V8m-9 4h4"/></svg>
|
||||
</div>
|
||||
<p style="font-size:13px;font-weight:600;color:#6b7280;margin-bottom:4px;">Tidak ada data arsip untuk tahun {{ $tahun }}</p>
|
||||
@if(request()->filled('q'))
|
||||
<p style="font-size:11.5px;color:#9ca3af;">Tidak ada hasil untuk "<strong>{{ request('q') }}</strong>"</p>
|
||||
<a href="{{ route('admin.data-warga.arsip', ['tahun' => $tahun]) }}" style="display:inline-block;margin-top:8px;font-size:12px;color:#2563eb;font-weight:600;text-decoration:none;">← Hapus pencarian</a>
|
||||
@endif
|
||||
</div>
|
||||
</td></tr>
|
||||
@endforelse
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
@if(isset($arsips) && method_exists($arsips,'links') && $arsips->hasPages())
|
||||
<div style="padding:10px 16px;border-top:1px solid #f1f5f9;background:#fafafa;">
|
||||
{{ $arsips->links() }}
|
||||
</div>
|
||||
@endif
|
||||
</div>
|
||||
|
||||
</x-app-layout>
|
||||
|
|
@ -6,14 +6,21 @@
|
|||
<h2 style="font-size:16px;font-weight:800;color:#0f172a;letter-spacing:-.02em;">Data Warga</h2>
|
||||
<p style="font-size:11px;color:#94a3b8;margin-top:2px;">Kelola dan pantau seluruh data warga dari semua dusun</p>
|
||||
</div>
|
||||
<div style="display:inline-flex;align-items:center;gap:6px;background:#f8fafc;border:1.5px solid #e2e8f0;border-radius:10px;padding:6px 12px;font-size:11px;color:#64748b;white-space:nowrap;">
|
||||
<svg width="13" height="13" fill="none" stroke="#3b82f6" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 7V3m8 4V3m-9 8h10M5 21h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v12a2 2 0 002 2z"/></svg>
|
||||
{{ \Carbon\Carbon::now('Asia/Jakarta')->translatedFormat('d F Y') }} — {{ \Carbon\Carbon::now('Asia/Jakarta')->format('H:i') }} WIB
|
||||
<div style="display:flex;align-items:center;gap:8px;flex-wrap:wrap;">
|
||||
<a href="{{ route('admin.data-warga.arsip') }}" style="display:inline-flex;align-items:center;gap:6px;background:#f1f5f9;border:1.5px solid #e2e8f0;border-radius:10px;padding:6px 12px;font-size:11px;color:#475569;font-weight:600;text-decoration:none;white-space:nowrap;">
|
||||
<svg width="13" height="13" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 8h14M5 8a2 2 0 110-4h14a2 2 0 110 4M5 8v10a2 2 0 002 2h10a2 2 0 002-2V8m-9 4h4"/></svg>
|
||||
Lihat Arsip
|
||||
</a>
|
||||
<div style="display:inline-flex;align-items:center;gap:6px;background:#f8fafc;border:1.5px solid #e2e8f0;border-radius:10px;padding:6px 12px;font-size:11px;color:#64748b;white-space:nowrap;">
|
||||
<svg width="13" height="13" fill="none" stroke="#3b82f6" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 7V3m8 4V3m-9 8h10M5 21h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v12a2 2 0 002 2z"/></svg>
|
||||
{{ \Carbon\Carbon::now('Asia/Jakarta')->translatedFormat('d F Y') }} — {{ \Carbon\Carbon::now('Asia/Jakarta')->format('H:i') }} WIB
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</x-slot>
|
||||
|
||||
<style>
|
||||
/* ── LIST VIEW ── */
|
||||
.dw-card{background:#fff;border-radius:16px;border:1.5px solid #f1f5f9;box-shadow:0 1px 4px rgba(0,0,0,.04),0 4px 16px rgba(0,0,0,.03);overflow:hidden;}
|
||||
.fl-box{display:flex;align-items:center;gap:9px;border-radius:12px;padding:10px 14px;font-size:12px;font-weight:500;}
|
||||
.fl-box svg{width:14px;height:14px;flex-shrink:0;}
|
||||
|
|
@ -51,42 +58,48 @@
|
|||
.btn-detail{background:#f3f4f6;color:#374151;}
|
||||
.btn-validasi{background:#eff6ff;color:#1d4ed8;}
|
||||
.btn-kirim{background:#fffbeb;color:#b45309;}
|
||||
.btn-reset{display:inline-flex;align-items:center;gap:6px;padding:8px 14px;background:#ef4444;color:#fff;font-size:12px;font-weight:700;border:none;border-radius:10px;cursor:pointer;white-space:nowrap;font-family:inherit;}
|
||||
.btn-reset:hover{filter:brightness(.93);}
|
||||
.info-chip{display:inline-flex;align-items:center;gap:4px;padding:3px 8px;border-radius:99px;font-size:10px;font-weight:700;background:#f8fafc;color:#475569;border:1px solid #e2e8f0;white-space:nowrap;}
|
||||
.m-backdrop{position:fixed;inset:0;z-index:40;background:rgba(0,0,0,.45);backdrop-filter:blur(3px);}
|
||||
.m-wrap{position:fixed;inset:0;z-index:50;overflow-y:auto;display:flex;align-items:flex-start;justify-content:center;padding:20px 12px 32px;}
|
||||
.m-box{width:100%;max-width:600px;background:#fff;border-radius:20px;box-shadow:0 24px 64px rgba(0,0,0,.18);overflow:hidden;margin:auto;}
|
||||
.mhero{position:relative;overflow:hidden;padding:16px 18px;background:linear-gradient(135deg,#1e40af,#2563eb,#3b82f6);}
|
||||
.mhero-b1{position:absolute;top:-20px;right:-20px;width:90px;height:90px;background:rgba(255,255,255,.08);border-radius:50%;}
|
||||
.mhero-b2{position:absolute;bottom:-22px;left:35%;width:70px;height:70px;background:rgba(255,255,255,.06);border-radius:50%;}
|
||||
.mhero-row{position:relative;z-index:1;display:flex;align-items:center;justify-content:space-between;gap:10px;}
|
||||
.m-av{width:42px;height:42px;border-radius:12px;background:rgba(255,255,255,.18);border:2px solid rgba(255,255,255,.25);display:flex;align-items:center;justify-content:center;font-size:17px;font-weight:900;color:#fff;flex-shrink:0;}
|
||||
.m-title{font-size:14px;font-weight:800;color:#fff;line-height:1.2;}
|
||||
.m-sub{font-size:11px;color:rgba(191,219,254,.85);margin-top:2px;}
|
||||
.m-close{width:30px;height:30px;border-radius:9px;background:rgba(255,255,255,.18);border:none;cursor:pointer;display:flex;align-items:center;justify-content:center;color:#fff;flex-shrink:0;}
|
||||
.m-close:hover{background:rgba(255,255,255,.28);}
|
||||
.m-close svg{width:14px;height:14px;stroke:currentColor;}
|
||||
.m-st-pill{margin-top:10px;position:relative;z-index:1;display:inline-flex;align-items:center;gap:5px;padding:4px 11px;border-radius:20px;background:rgba(255,255,255,.18);color:#fff;border:1px solid rgba(255,255,255,.25);font-size:11px;font-weight:700;}
|
||||
.m-body{padding:12px;background:#f8fafc;display:flex;flex-direction:column;gap:10px;}
|
||||
.msec{background:#fff;border-radius:13px;border:1.5px solid #f1f5f9;overflow:hidden;}
|
||||
.msec-h{padding:8px 14px;border-bottom:1px solid #f1f5f9;background:#fafafa;display:flex;align-items:center;gap:7px;}
|
||||
.msec-h .bar{width:3px;height:13px;border-radius:3px;flex-shrink:0;}
|
||||
.msec-h h4{font-size:10px;font-weight:700;color:#6b7280;text-transform:uppercase;letter-spacing:.08em;}
|
||||
.msec-b{padding:12px 14px;}
|
||||
.mg2{display:grid;grid-template-columns:1fr 1fr;gap:10px 16px;}
|
||||
.mlbl{font-size:10px;color:#9ca3af;text-transform:uppercase;letter-spacing:.07em;margin-bottom:2px;}
|
||||
.mval{font-size:12.5px;font-weight:600;color:#111827;}
|
||||
.mval-mono{font-family:monospace;font-size:11px;background:#f3f4f6;border:1px solid #e5e7eb;border-radius:7px;padding:3px 8px;color:#374151;display:inline-block;word-break:break-all;}
|
||||
.m-footer{padding:10px 16px;border-top:1px solid #f1f5f9;background:#fff;display:flex;justify-content:flex-end;}
|
||||
.btn-close-m{display:inline-flex;align-items:center;gap:6px;padding:8px 18px;font-size:12.5px;font-weight:700;background:#f3f4f6;color:#374151;border:none;border-radius:10px;cursor:pointer;font-family:inherit;}
|
||||
.btn-close-m:hover{background:#e5e7eb;}
|
||||
|
||||
/* ── DETAIL PANEL ── */
|
||||
#detailPanel{display:none;margin-top:16px;}
|
||||
.hero-banner{background:linear-gradient(135deg,#1e40af 0%,#2563eb 60%,#3b82f6 100%);border-radius:1rem;padding:1.5rem;position:relative;overflow:hidden;margin-bottom:1.25rem;}
|
||||
.hero-bubble{position:absolute;border-radius:50%;pointer-events:none;}
|
||||
.detail-card{background:#fff;border-radius:1rem;border:1px solid #f1f5f9;margin-bottom:1rem;overflow:hidden;}
|
||||
.detail-card-head{padding:.65rem 1.1rem;border-bottom:1px solid #f1f5f9;display:flex;align-items:center;gap:8px;background:#f9fafb;}
|
||||
.detail-card-accent{width:3px;height:16px;border-radius:2px;flex-shrink:0;}
|
||||
.detail-card-title{font-size:11px;font-weight:600;text-transform:uppercase;letter-spacing:.06em;color:#6b7280;}
|
||||
.detail-card-body{padding:1rem 1.1rem;}
|
||||
.data-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(140px,1fr));gap:1rem;}
|
||||
.data-lbl{font-size:10px;font-weight:600;text-transform:uppercase;letter-spacing:.05em;color:#9ca3af;margin-bottom:3px;}
|
||||
.data-val{font-size:13px;color:#111827;font-weight:500;}
|
||||
.badge-rt{display:inline-flex;align-items:center;padding:3px 10px;border-radius:20px;font-size:12px;background:#dbeafe;color:#1e40af;font-weight:600;}
|
||||
.faktor-item{display:flex;align-items:flex-start;gap:8px;padding:7px 10px;border-radius:8px;font-size:13px;margin-bottom:5px;}
|
||||
.faktor-pos{background:#f0fdf4;color:#166534;}
|
||||
.faktor-neg{background:#fff1f2;color:#9f1239;}
|
||||
.penjelasan-row{display:flex;justify-content:space-between;align-items:center;padding:7px 0;font-size:13px;border-bottom:1px solid #f1f5f9;}
|
||||
.penjelasan-row:last-child{border-bottom:none;}
|
||||
.ringkasan-box{background:#f0f9ff;border:1px solid #bae6fd;border-radius:10px;padding:12px 14px;font-size:13px;color:#0c4a6e;line-height:1.6;}
|
||||
.foto-grid{display:grid;grid-template-columns:repeat(4,1fr);gap:8px;}
|
||||
@media(max-width:600px){.foto-grid{grid-template-columns:repeat(2,1fr);}}
|
||||
.foto-thumb{aspect-ratio:1;border-radius:8px;overflow:hidden;border:1px solid #e5e7eb;position:relative;cursor:pointer;}
|
||||
.foto-thumb img{width:100%;height:100%;object-fit:cover;display:block;}
|
||||
.foto-placeholder{width:100%;height:100%;background:#f9fafb;display:flex;flex-direction:column;align-items:center;justify-content:center;gap:4px;}
|
||||
.foto-label{position:absolute;bottom:0;left:0;right:0;background:rgba(0,0,0,.5);color:#fff;font-size:10px;text-align:center;padding:3px 4px;}
|
||||
.detail-layout{display:grid;grid-template-columns:1fr 300px;gap:1rem;align-items:start;}
|
||||
@media(max-width:900px){.detail-layout{grid-template-columns:1fr;}}
|
||||
.badge-layak{background:#dcfce7;color:#15803d;padding:5px 14px;border-radius:20px;font-size:13px;font-weight:600;display:inline-flex;align-items:center;gap:5px;}
|
||||
.badge-tl{background:#fee2e2;color:#b91c1c;padding:5px 14px;border-radius:20px;font-size:13px;font-weight:600;display:inline-flex;align-items:center;gap:5px;}
|
||||
.progress-track{height:6px;border-radius:3px;background:#e5e7eb;overflow:hidden;}
|
||||
.donut-ring{transform:rotate(-90deg);transform-origin:36px 36px;}
|
||||
|
||||
/* lightbox */
|
||||
#dw-lightbox{position:fixed;inset:0;z-index:999;background:rgba(0,0,0,.85);display:none;align-items:center;justify-content:center;padding:16px;}
|
||||
#dw-lightbox img{max-width:100%;max-height:100%;border-radius:12px;object-fit:contain;}
|
||||
|
||||
@media(max-width:640px){
|
||||
.mg2{grid-template-columns:1fr;}
|
||||
.mg2 [style*="grid-column:span 2"]{grid-column:span 1 !important;}
|
||||
.m-wrap{padding:12px 8px 24px;}
|
||||
.m-box{border-radius:16px;}
|
||||
.msec-b{padding:10px 12px;}
|
||||
.detail-card-body{padding:.75rem;}
|
||||
.data-grid{grid-template-columns:repeat(auto-fill,minmax(120px,1fr));gap:.75rem;}
|
||||
.hero-banner{padding:1rem;}
|
||||
}
|
||||
</style>
|
||||
|
||||
|
|
@ -103,46 +116,32 @@
|
|||
</div>
|
||||
@endif
|
||||
|
||||
<div class="dw-card" style="padding:14px 16px;">
|
||||
{{-- SEARCH & FILTER --}}
|
||||
<div class="dw-card" style="padding:14px 16px;margin-bottom:12px;">
|
||||
<div style="display:flex;align-items:center;gap:7px;margin-bottom:10px;">
|
||||
<div style="width:3px;height:14px;background:#2563eb;border-radius:4px;"></div>
|
||||
<h3 style="font-size:12px;font-weight:700;color:#374151;">Pencarian & Filter</h3>
|
||||
</div>
|
||||
|
||||
<form method="GET" action="{{ url()->current() }}"
|
||||
style="display:flex;gap:8px;align-items:center;flex-wrap:wrap;">
|
||||
<form method="GET" action="{{ url()->current() }}" style="display:flex;gap:8px;align-items:center;flex-wrap:wrap;">
|
||||
<div class="sw" style="flex:1;min-width:200px;">
|
||||
<svg fill="none" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z"/></svg>
|
||||
<input type="text" name="q" value="{{ request('q') }}" placeholder="Cari NIK, Nama, atau Email...">
|
||||
</div>
|
||||
<button type="submit"
|
||||
style="display:inline-flex;align-items:center;gap:6px;padding:9px 16px;background:#2563eb;color:#fff;font-size:12px;font-weight:700;border:none;border-radius:11px;cursor:pointer;white-space:nowrap;font-family:inherit;">
|
||||
<button type="submit" style="display:inline-flex;align-items:center;gap:6px;padding:9px 16px;background:#2563eb;color:#fff;font-size:12px;font-weight:700;border:none;border-radius:11px;cursor:pointer;white-space:nowrap;font-family:inherit;">
|
||||
<svg width="13" height="13" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z"/></svg>
|
||||
Cari
|
||||
</button>
|
||||
@if(request()->filled('q'))
|
||||
<a href="{{ url()->current() }}"
|
||||
style="display:inline-flex;align-items:center;justify-content:center;width:36px;height:36px;background:#f3f4f6;border-radius:10px;color:#6b7280;text-decoration:none;">
|
||||
<a href="{{ url()->current() }}" style="display:inline-flex;align-items:center;justify-content:center;width:36px;height:36px;background:#f3f4f6;border-radius:10px;color:#6b7280;text-decoration:none;">
|
||||
<svg width="14" height="14" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12"/></svg>
|
||||
</a>
|
||||
@endif
|
||||
</form>
|
||||
|
||||
<div style="margin-top:12px;display:flex;justify-content:flex-end;">
|
||||
<form method="POST" action="{{ route('admin.data-warga.bersihkan') }}"
|
||||
onsubmit="return confirm('Yakin ingin menghapus semua data warga aktif? Data pada laporan arsip tidak akan terhapus.');">
|
||||
@csrf @method('DELETE')
|
||||
<button type="submit" class="btn-reset">
|
||||
<svg width="13" height="13" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16"/>
|
||||
</svg>
|
||||
Bersihkan Data Warga
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="dw-card">
|
||||
{{-- TABLE --}}
|
||||
<div class="dw-card" id="tableCard">
|
||||
<div style="padding:10px 16px;border-bottom:1px solid #f1f5f9;background:#fafafa;display:flex;align-items:center;justify-content:space-between;flex-wrap:wrap;gap:6px;">
|
||||
<div style="display:flex;align-items:center;gap:7px;">
|
||||
<div style="width:3px;height:15px;background:#2563eb;border-radius:4px;"></div>
|
||||
|
|
@ -180,7 +179,7 @@
|
|||
|
||||
$positive=[]; $negative=[];
|
||||
if(strtolower($warga->pekerjaan??'')==='tidak bekerja') $positive[]='Tidak bekerja';
|
||||
elseif(in_array(strtolower($warga->pekerjaan??''),['buruh harian','buruh','petani','nelayan'])) $positive[]='Pekerjaan tergolong rentan';
|
||||
elseif(in_array(strtolower($warga->pekerjaan??''),['buruh harian lepas','buruh harian','buruh','petani kecil','petani','mengurus rumah tangga'])) $positive[]='Pekerjaan tergolong rentan';
|
||||
else $negative[]='Memiliki pekerjaan relatif stabil';
|
||||
if(($warga->penghasilan??0)<=1000000) $positive[]='Penghasilan rendah';
|
||||
elseif(($warga->penghasilan??0)<=2000000) $positive[]='Penghasilan tergolong rendah';
|
||||
|
|
@ -197,8 +196,6 @@
|
|||
else $positive[]='Belum menerima bantuan lain';
|
||||
if(($warga->usia??0)>=60) $positive[]='Usia lanjut';
|
||||
elseif(($warga->usia??0)>=45) $positive[]='Usia cukup rentan';
|
||||
|
||||
// Parameter baru
|
||||
if(strtolower($warga->kondisi_rumah??'')==='tidak layak') $positive[]='Kondisi rumah tidak layak huni';
|
||||
elseif(strtolower($warga->kondisi_rumah??'')==='sedang') $positive[]='Kondisi rumah sedang';
|
||||
else $negative[]='Kondisi rumah layak';
|
||||
|
|
@ -208,7 +205,6 @@
|
|||
if(strtolower($warga->sumber_air??'')==='sungai') $positive[]='Sumber air sungai';
|
||||
elseif(strtolower($warga->sumber_air??'')==='sumur') $positive[]='Sumber air sumur';
|
||||
else $negative[]='Sumber air PDAM';
|
||||
|
||||
$summary='Nilai kelayakan dihitung berdasarkan pekerjaan, penghasilan, tanggungan, aset, bantuan lain, usia, kondisi rumah, meteran listrik, dan sumber air.';
|
||||
if(count($positive)&&count($negative)) $summary.=' Terdapat faktor pendukung dan pengurang.';
|
||||
elseif(count($positive)) $summary.=' Faktor mayoritas mendukung kelayakan.';
|
||||
|
|
@ -223,6 +219,11 @@
|
|||
'alamat'=>$warga->alamat??'-','desa'=>$warga->desa??'-',
|
||||
'aset_kepemilikan'=>$warga->aset_kepemilikan??'-',
|
||||
'kondisi_rumah'=>$warga->kondisi_rumah??'-',
|
||||
'lantai_rumah'=>$warga->lantai_rumah??'-',
|
||||
'dinding_rumah'=>$warga->dinding_rumah??'-',
|
||||
'atap_rumah'=>$warga->atap_rumah??'-',
|
||||
'luas_rumah_m2'=>$warga->luas_rumah_m2??'-',
|
||||
'status_kepemilikan_rumah'=>$warga->status_kepemilikan_rumah??'-',
|
||||
'meteran_listrik'=>$warga->meteran_listrik??'-',
|
||||
'sumber_air'=>$warga->sumber_air??'-',
|
||||
'pekerjaan'=>$warga->pekerjaan??'-','penghasilan'=>$warga->penghasilan??0,
|
||||
|
|
@ -233,7 +234,16 @@
|
|||
'recommendation'=>optional($warga->prediksiKelayakan)->recommendation,
|
||||
'positive'=>$positive,'negative'=>$negative,'summary'=>$summary,
|
||||
'input_oleh'=>optional($warga->user)->name??(optional($warga->user)->email??'-'),
|
||||
'created_at'=>optional($warga->created_at)->translatedFormat('d F Y, H:i')??'-'
|
||||
'created_at'=>optional($warga->created_at)->translatedFormat('d F Y, H:i')??'-',
|
||||
'foto_rumah_depan' => $warga->foto_rumah_depan ? asset('storage/'.$warga->foto_rumah_depan) : null,
|
||||
'foto_rumah_belakang' => $warga->foto_rumah_belakang ? asset('storage/'.$warga->foto_rumah_belakang) : null,
|
||||
'foto_rumah_kanan' => $warga->foto_rumah_kanan ? asset('storage/'.$warga->foto_rumah_kanan) : null,
|
||||
'foto_rumah_kiri' => $warga->foto_rumah_kiri ? asset('storage/'.$warga->foto_rumah_kiri) : null,
|
||||
'foto_kk' => $warga->foto_kk ? asset('storage/'.$warga->foto_kk) : null,
|
||||
'foto_ktp' => $warga->foto_ktp ? asset('storage/'.$warga->foto_ktp) : null,
|
||||
'foto_rekening_listrik' => $warga->foto_rekening_listrik ? asset('storage/'.$warga->foto_rekening_listrik) : null,
|
||||
'foto_meteran_air' => $warga->foto_meteran_air ? asset('storage/'.$warga->foto_meteran_air) : null,
|
||||
'dokumen_pendukung' => $warga->dokumen_pendukung ? asset('storage/'.$warga->dokumen_pendukung) : null,
|
||||
];
|
||||
$trk=$warga->tracking_status??'';
|
||||
@endphp
|
||||
|
|
@ -258,9 +268,7 @@
|
|||
<td>
|
||||
@if($probPct !== null)
|
||||
<div class="prob-wrap">
|
||||
<div class="prob-bg">
|
||||
<div class="prob-bar" style="width:{{ min($probPct,100) }}%;background:{{ $sc }};"></div>
|
||||
</div>
|
||||
<div class="prob-bg"><div class="prob-bar" style="width:{{ min($probPct,100) }}%;background:{{ $sc }};"></div></div>
|
||||
<span style="font-size:11px;font-weight:700;color:{{ $sc }};white-space:nowrap;">{{ number_format($probPct,1) }}%</span>
|
||||
</div>
|
||||
@else
|
||||
|
|
@ -268,50 +276,128 @@
|
|||
@endif
|
||||
</td>
|
||||
<td>
|
||||
@if($trk==='terkirim')
|
||||
<span class="badge trk-terkirim"><span class="dot"></span>Terkirim</span>
|
||||
@elseif($trk==='sedang_validasi')
|
||||
<span class="badge trk-validasi"><span class="dot"></span>Validasi</span>
|
||||
@elseif($trk==='selesai')
|
||||
<span class="badge trk-selesai"><span class="dot"></span>Selesai</span>
|
||||
@else
|
||||
<span class="badge trk-default"><span class="dot"></span>—</span>
|
||||
@if($trk==='terkirim') <span class="badge trk-terkirim"><span class="dot"></span>Terkirim</span>
|
||||
@elseif($trk==='sedang_validasi') <span class="badge trk-validasi"><span class="dot"></span>Validasi</span>
|
||||
@elseif($trk==='selesai') <span class="badge trk-selesai"><span class="dot"></span>Selesai</span>
|
||||
@elseif($trk==='ditinjau_ulang') <span class="badge" style="background:#f5f3ff;color:#6d28d9;border:1px solid #ddd6fe;"><span class="dot" style="background:#7c3aed;"></span>Ditinjau Ulang</span>
|
||||
@else <span class="badge trk-default"><span class="dot"></span>—</span>
|
||||
@endif
|
||||
</td>
|
||||
<td>
|
||||
<div style="display:flex;align-items:center;justify-content:flex-end;gap:4px;flex-wrap:wrap;">
|
||||
<button type="button" onclick="openDetailModal({{ $warga->id }})" class="btn-act btn-detail">
|
||||
|
||||
{{-- Tombol Detail (selalu muncul) --}}
|
||||
<button type="button" onclick="openDetail({{ $warga->id }})" class="btn-act btn-detail">
|
||||
<svg fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 12a3 3 0 11-6 0 3 3 0 016 0z"/><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M2.458 12C3.732 7.943 7.523 5 12 5c4.478 0 8.268 2.943 9.542 7-1.274 4.057-5.064 7-9.542 7-4.477 0-8.268-2.943-9.542-7z"/></svg>
|
||||
Detail
|
||||
</button>
|
||||
@if($trk==='terkirim')
|
||||
<form method="POST" action="{{ route('admin.data-warga.mulai-validasi',$warga->id) }}">
|
||||
|
||||
{{-- Status: terkirim → Validasi + Terima Langsung + Tolak Langsung --}}
|
||||
@if($trk === 'terkirim')
|
||||
<form method="POST" action="{{ route('admin.data-warga.mulai-validasi', $warga->id) }}">
|
||||
@csrf
|
||||
<button type="submit" class="btn-act btn-validasi">
|
||||
<svg fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5H7a2 2 0 00-2 2v10a2 2 0 002 2h10a2 2 0 002-2v-2m-1-10l-7 7-4 1 1-4 7-7a2.828 2.828 0 114 4z"/></svg>
|
||||
Validasi
|
||||
</button>
|
||||
</form>
|
||||
<form method="POST" action="{{ route('admin.data-warga.terima', $warga->id) }}" onsubmit="return confirm('Terima langsung pengajuan {{ addslashes($warga->nama_lengkap) }}?')">
|
||||
@csrf
|
||||
<button type="submit" class="btn-act" style="background:#dcfce7;color:#15803d;">
|
||||
<svg fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 13l4 4L19 7"/></svg>
|
||||
Terima
|
||||
</button>
|
||||
</form>
|
||||
<form method="POST" action="{{ route('admin.data-warga.tolak', $warga->id) }}" onsubmit="return confirm('Tolak langsung pengajuan {{ addslashes($warga->nama_lengkap) }}?')">
|
||||
@csrf
|
||||
<button type="submit" class="btn-act" style="background:#fee2e2;color:#b91c1c;">
|
||||
<svg fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12"/></svg>
|
||||
Tolak
|
||||
</button>
|
||||
</form>
|
||||
@endif
|
||||
@if($trk==='sedang_validasi')
|
||||
|
||||
{{-- Status: sedang_validasi → info + Terima/Tolak Langsung --}}
|
||||
@if($trk === 'sedang_validasi')
|
||||
<span class="info-chip">Lanjut filterisasi</span>
|
||||
<form method="POST" action="{{ route('admin.data-warga.terima', $warga->id) }}" onsubmit="return confirm('Terima langsung pengajuan {{ addslashes($warga->nama_lengkap) }}?')">
|
||||
@csrf
|
||||
<button type="submit" class="btn-act" style="background:#dcfce7;color:#15803d;">
|
||||
<svg fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 13l4 4L19 7"/></svg>
|
||||
Terima
|
||||
</button>
|
||||
</form>
|
||||
<form method="POST" action="{{ route('admin.data-warga.tolak', $warga->id) }}" onsubmit="return confirm('Tolak langsung pengajuan {{ addslashes($warga->nama_lengkap) }}?')">
|
||||
@csrf
|
||||
<button type="submit" class="btn-act" style="background:#fee2e2;color:#b91c1c;">
|
||||
<svg fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12"/></svg>
|
||||
Tolak
|
||||
</button>
|
||||
</form>
|
||||
@endif
|
||||
@if($trk==='selesai')
|
||||
@if(($warga->status_verifikasi??'') === 'disetujui')
|
||||
|
||||
{{-- Status: ditinjau_ulang → sama seperti terkirim, bisa Validasi/Terima/Tolak --}}
|
||||
@if($trk === 'ditinjau_ulang')
|
||||
<form method="POST" action="{{ route('admin.data-warga.mulai-validasi', $warga->id) }}">
|
||||
@csrf
|
||||
<button type="submit" class="btn-act btn-validasi">
|
||||
<svg fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5H7a2 2 0 00-2 2v10a2 2 0 002 2h10a2 2 0 002-2v-2m-1-10l-7 7-4 1 1-4 7-7a2.828 2.828 0 114 4z"/></svg>
|
||||
Validasi
|
||||
</button>
|
||||
</form>
|
||||
<form method="POST" action="{{ route('admin.data-warga.terima', $warga->id) }}" onsubmit="return confirm('Terima langsung pengajuan {{ addslashes($warga->nama_lengkap) }}?')">
|
||||
@csrf
|
||||
<button type="submit" class="btn-act" style="background:#dcfce7;color:#15803d;">
|
||||
<svg fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 13l4 4L19 7"/></svg>
|
||||
Terima
|
||||
</button>
|
||||
</form>
|
||||
<form method="POST" action="{{ route('admin.data-warga.tolak', $warga->id) }}" onsubmit="return confirm('Tolak langsung pengajuan {{ addslashes($warga->nama_lengkap) }}?')">
|
||||
@csrf
|
||||
<button type="submit" class="btn-act" style="background:#fee2e2;color:#b91c1c;">
|
||||
<svg fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12"/></svg>
|
||||
Tolak
|
||||
</button>
|
||||
</form>
|
||||
@endif
|
||||
|
||||
{{-- Status: selesai → badge hasil + Kirim Hasil + Aktifkan Kembali + Arsipkan --}}
|
||||
@if($trk === 'selesai')
|
||||
@if(($warga->status_verifikasi??'')==='disetujui')
|
||||
<span class="badge stat-yes"><span class="dot" style="background:#22c55e;"></span>Diterima</span>
|
||||
@elseif(($warga->status_verifikasi??'') === 'ditolak')
|
||||
@elseif(($warga->status_verifikasi??'')==='ditolak')
|
||||
<span class="badge stat-no"><span class="dot" style="background:#f43f5e;"></span>Ditolak</span>
|
||||
@else
|
||||
<span class="badge stat-pending"><span class="dot" style="background:#f59e0b;"></span>Pending</span>
|
||||
@endif
|
||||
<form method="POST" action="{{ route('admin.data-warga.selesai-validasi',$warga->id) }}">
|
||||
|
||||
<form method="POST" action="{{ route('admin.data-warga.selesai-validasi', $warga->id) }}">
|
||||
@csrf
|
||||
<button type="submit" class="btn-act btn-kirim">
|
||||
<svg fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z"/></svg>
|
||||
Kirim Hasil
|
||||
</button>
|
||||
</form>
|
||||
|
||||
<form method="POST" action="{{ route('admin.data-warga.aktifkan-kembali', $warga->id) }}" onsubmit="return confirm('Aktifkan kembali pengajuan {{ addslashes($warga->nama_lengkap) }}?')">
|
||||
@csrf
|
||||
<button type="submit" class="btn-act" style="background:#fffbeb;color:#b45309;">
|
||||
<svg fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 4v5h.582m15.356 2A8.001 8.001 0 004.582 9m0 0H9m11 11v-5h-.581m0 0a8.003 8.003 0 01-15.357-2m15.357 2H15"/></svg>
|
||||
Aktifkan Kembali
|
||||
</button>
|
||||
</form>
|
||||
|
||||
@if(!$warga->arsip_tahun)
|
||||
<form method="POST" action="{{ route('admin.data-warga.arsipkan', $warga->id) }}" onsubmit="return confirm('Arsipkan data {{ addslashes($warga->nama_lengkap) }} ke tahun {{ now()->year }}?')">
|
||||
@csrf
|
||||
<button type="submit" class="btn-act" style="background:#f3f4f6;color:#6b7280;">
|
||||
<svg fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 8h14M5 8a2 2 0 110-4h14a2 2 0 110 4M5 8v10a2 2 0 002 2h10a2 2 0 002-2V8m-9 4h4"/></svg>
|
||||
Arsipkan
|
||||
</button>
|
||||
</form>
|
||||
@endif
|
||||
@endif
|
||||
|
||||
</div>
|
||||
<script type="application/json" id="warga-{{ $warga->id }}">{!! json_encode($dp,JSON_UNESCAPED_UNICODE|JSON_UNESCAPED_SLASHES) !!}</script>
|
||||
</td>
|
||||
|
|
@ -341,205 +427,365 @@
|
|||
@endif
|
||||
</div>
|
||||
|
||||
{{-- MODAL --}}
|
||||
<div id="detailBackdrop" class="m-backdrop" style="display:none;" onclick="closeDetailModal()"></div>
|
||||
<div id="detailModal" class="m-wrap" style="display:none;">
|
||||
<div class="m-box">
|
||||
{{-- DETAIL PANEL --}}
|
||||
<div id="detailPanel">
|
||||
|
||||
<div class="mhero">
|
||||
<div class="mhero-b1"></div><div class="mhero-b2"></div>
|
||||
<div class="mhero-row">
|
||||
<div style="display:flex;align-items:center;gap:10px;min-width:0;">
|
||||
<div class="m-av"><span id="modal-initial">?</span></div>
|
||||
<div style="min-width:0;">
|
||||
<div class="m-title" id="modal-title">—</div>
|
||||
<div class="m-sub" id="modal-subtitle">—</div>
|
||||
{{-- Tombol kembali --}}
|
||||
<div style="margin-bottom:12px;">
|
||||
<button onclick="closeDetail()" style="display:inline-flex;align-items:center;gap:6px;padding:8px 14px;background:#fff;border:1.5px solid #e2e8f0;border-radius:10px;font-size:12px;font-weight:600;color:#374151;cursor:pointer;font-family:inherit;">
|
||||
<svg width="14" height="14" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 19l-7-7 7-7"/></svg>
|
||||
Kembali ke Daftar
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{{-- HERO --}}
|
||||
<div class="hero-banner">
|
||||
<div class="hero-bubble" style="width:180px;height:180px;background:rgba(255,255,255,.06);top:-40px;right:-30px;"></div>
|
||||
<div class="hero-bubble" style="width:90px;height:90px;background:rgba(255,255,255,.04);top:50px;right:100px;"></div>
|
||||
<div style="display:flex;align-items:flex-start;justify-content:space-between;gap:12px;flex-wrap:wrap;">
|
||||
<div style="display:flex;align-items:center;gap:12px;">
|
||||
<div id="dp-avatar" style="width:48px;height:48px;border-radius:50%;background:rgba(255,255,255,.2);display:flex;align-items:center;justify-content:center;color:#fff;font-size:20px;font-weight:700;flex-shrink:0;">?</div>
|
||||
<div>
|
||||
<div id="dp-nama" style="color:#fff;font-size:18px;font-weight:700;line-height:1.2;">—</div>
|
||||
<div id="dp-nik" style="color:rgba(255,255,255,.7);font-size:12px;margin-top:2px;">—</div>
|
||||
<div style="margin-top:6px;">
|
||||
<span id="dp-status-pill" style="display:inline-flex;align-items:center;gap:5px;padding:3px 10px;border-radius:20px;font-size:11px;font-weight:600;background:rgba(255,255,255,.15);color:#fff;border:1px solid rgba(255,255,255,.25);">—</span>
|
||||
</div>
|
||||
</div>
|
||||
<button class="m-close" onclick="closeDetailModal()">
|
||||
<svg fill="none" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12"/></svg>
|
||||
</button>
|
||||
</div>
|
||||
<div class="m-st-pill">
|
||||
<span style="width:6px;height:6px;border-radius:50%;flex-shrink:0;" id="modal-status-dot"></span>
|
||||
<span id="modal-status-text">—</span>
|
||||
<div style="text-align:right;">
|
||||
<div id="dp-prob-big" style="color:#fff;font-size:40px;font-weight:800;line-height:1;">—</div>
|
||||
<div style="color:rgba(255,255,255,.6);font-size:11px;margin-top:2px;">probabilitas</div>
|
||||
<div style="margin-top:6px;" id="dp-reco-hero"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="m-body">
|
||||
{{-- DETAIL LAYOUT --}}
|
||||
<div class="detail-layout">
|
||||
|
||||
{{-- KOLOM KIRI --}}
|
||||
<div>
|
||||
|
||||
{{-- Identitas --}}
|
||||
<div class="msec">
|
||||
<div class="msec-h"><div class="bar" style="background:#2563eb;"></div><h4>Data Identitas</h4></div>
|
||||
<div class="msec-b">
|
||||
<div class="mg2">
|
||||
<div><p class="mlbl">NIK</p><span id="d_nik" class="mval mval-mono">-</span></div>
|
||||
<div><p class="mlbl">No. KK</p><span id="d_no_kk" class="mval mval-mono">-</span></div>
|
||||
<div><p class="mlbl">Nama Lengkap</p><p id="d_nama" class="mval">-</p></div>
|
||||
<div><p class="mlbl">Jenis Kelamin</p><p id="d_jk" class="mval">-</p></div>
|
||||
<div><p class="mlbl">Tempat, Tgl Lahir</p><p id="d_lahir" class="mval">-</p></div>
|
||||
<div><p class="mlbl">Usia</p><p id="d_usia" class="mval">-</p></div>
|
||||
<div><p class="mlbl">Status Perkawinan</p><p id="d_kawin" class="mval">-</p></div>
|
||||
<div class="detail-card">
|
||||
<div class="detail-card-head"><div class="detail-card-accent" style="background:#2563eb;"></div><span class="detail-card-title">Identitas</span></div>
|
||||
<div class="detail-card-body">
|
||||
<div class="data-grid">
|
||||
<div><div class="data-lbl">No. KK</div><div class="data-val" id="dp-no_kk">—</div></div>
|
||||
<div><div class="data-lbl">Jenis Kelamin</div><div class="data-val" id="dp-jk">—</div></div>
|
||||
<div><div class="data-lbl">Tempat Lahir</div><div class="data-val" id="dp-tempat_lahir">—</div></div>
|
||||
<div><div class="data-lbl">Tanggal Lahir</div><div class="data-val" id="dp-tanggal_lahir">—</div></div>
|
||||
<div><div class="data-lbl">Usia</div><div class="data-val" id="dp-usia">—</div></div>
|
||||
<div><div class="data-lbl">Status Kawin</div><div class="data-val" id="dp-kawin">—</div></div>
|
||||
<div><div class="data-lbl">Input Oleh</div><div class="data-val" id="dp-input_oleh">—</div></div>
|
||||
<div><div class="data-lbl">Didaftarkan</div><div class="data-val" id="dp-created_at">—</div></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{-- Tempat Tinggal --}}
|
||||
<div class="msec">
|
||||
<div class="msec-h"><div class="bar" style="background:#10b981;"></div><h4>Tempat Tinggal</h4></div>
|
||||
<div class="msec-b">
|
||||
<div class="mg2">
|
||||
<div style="grid-column:span 2;"><p class="mlbl">Alamat</p><p id="d_alamat" class="mval">-</p></div>
|
||||
<div><p class="mlbl">Dusun</p><p id="d_dusun" class="mval">-</p></div>
|
||||
<div><p class="mlbl">RT</p><p id="d_rt" class="mval">-</p></div>
|
||||
<div style="grid-column:span 2;"><p class="mlbl">Aset Kepemilikan</p><p id="d_aset" class="mval">-</p></div>
|
||||
<div><p class="mlbl">Kondisi Rumah</p><p id="d_kondisi_rumah" class="mval">-</p></div>
|
||||
<div><p class="mlbl">Meteran Listrik</p><p id="d_meteran_listrik" class="mval">-</p></div>
|
||||
<div><p class="mlbl">Sumber Air</p><p id="d_sumber_air" class="mval">-</p></div>
|
||||
<div class="detail-card">
|
||||
<div class="detail-card-head"><div class="detail-card-accent" style="background:#10b981;"></div><span class="detail-card-title">Tempat Tinggal</span></div>
|
||||
<div class="detail-card-body">
|
||||
<div class="data-grid">
|
||||
<div style="grid-column:span 2;"><div class="data-lbl">Alamat</div><div class="data-val" id="dp-alamat">—</div></div>
|
||||
<div><div class="data-lbl">Dusun</div><div class="data-val" id="dp-dusun">—</div></div>
|
||||
<div><div class="data-lbl">RT</div><div class="data-val" id="dp-rt">—</div></div>
|
||||
<div><div class="data-lbl">Aset Kepemilikan</div><div class="data-val" id="dp-aset">—</div></div>
|
||||
<div><div class="data-lbl">Lantai</div><div class="data-val" id="dp-lantai">—</div></div>
|
||||
<div><div class="data-lbl">Dinding</div><div class="data-val" id="dp-dinding">—</div></div>
|
||||
<div><div class="data-lbl">Atap</div><div class="data-val" id="dp-atap">—</div></div>
|
||||
<div><div class="data-lbl">Luas Rumah</div><div class="data-val" id="dp-luas">—</div></div>
|
||||
<div><div class="data-lbl">Status Kepemilikan</div><div class="data-val" id="dp-status_rumah">—</div></div>
|
||||
<div><div class="data-lbl">Meteran Listrik</div><div class="data-val" id="dp-meteran">—</div></div>
|
||||
<div><div class="data-lbl">Sumber Air</div><div class="data-val" id="dp-air">—</div></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{-- Ekonomi --}}
|
||||
<div class="msec">
|
||||
<div class="msec-h"><div class="bar" style="background:#f59e0b;"></div><h4>Data Ekonomi</h4></div>
|
||||
<div class="msec-b">
|
||||
<div class="mg2">
|
||||
<div><p class="mlbl">Pekerjaan</p><p id="d_pekerjaan" class="mval">-</p></div>
|
||||
<div><p class="mlbl">Penghasilan</p><p id="d_penghasilan" class="mval">-</p></div>
|
||||
<div><p class="mlbl">Tanggungan</p><p id="d_tanggungan" class="mval">-</p></div>
|
||||
<div><p class="mlbl">Bantuan Lain</p><p id="d_bantuan" class="mval">-</p></div>
|
||||
<div class="detail-card">
|
||||
<div class="detail-card-head"><div class="detail-card-accent" style="background:#f59e0b;"></div><span class="detail-card-title">Data Ekonomi</span></div>
|
||||
<div class="detail-card-body">
|
||||
<div class="data-grid">
|
||||
<div><div class="data-lbl">Pekerjaan</div><div class="data-val" id="dp-pekerjaan">—</div></div>
|
||||
<div><div class="data-lbl">Penghasilan</div><div class="data-val" id="dp-penghasilan">—</div></div>
|
||||
<div><div class="data-lbl">Tanggungan</div><div class="data-val" id="dp-tanggungan">—</div></div>
|
||||
<div><div class="data-lbl">Bantuan Lain</div><div class="data-val" id="dp-bantuan">—</div></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{-- Prediksi --}}
|
||||
<div class="msec">
|
||||
<div class="msec-h"><div class="bar" style="background:#2563eb;"></div><h4>Prediksi Kelayakan</h4></div>
|
||||
<div class="msec-b">
|
||||
<div id="d_pred_empty" style="display:none;padding:8px 0;text-align:center;font-size:11.5px;color:#9ca3af;">Belum ada data prediksi.</div>
|
||||
<div id="d_pred_wrap">
|
||||
<div style="display:flex;align-items:center;gap:12px;margin-bottom:10px;">
|
||||
<div style="font-size:26px;font-weight:900;color:#111827;line-height:1;" id="d_prob">—</div>
|
||||
<div style="flex:1;height:6px;background:#f1f5f9;border-radius:99px;overflow:hidden;">
|
||||
<div id="d_prob_bar" style="height:100%;border-radius:99px;width:0%;transition:width .5s;background:#10b981;"></div>
|
||||
{{-- Foto & Dokumen --}}
|
||||
<div class="detail-card">
|
||||
<div class="detail-card-head"><div class="detail-card-accent" style="background:#e11d48;"></div><span class="detail-card-title">Foto & Dokumen</span></div>
|
||||
<div class="detail-card-body">
|
||||
<div class="foto-grid" id="dp-foto-grid"></div>
|
||||
<div id="dp-dokumen-wrap" style="display:none;margin-top:10px;padding-top:10px;border-top:1px solid #f1f5f9;">
|
||||
<a id="dp-dokumen-link" href="#" target="_blank" style="display:inline-flex;align-items:center;gap:6px;font-size:13px;color:#2563eb;font-weight:600;">
|
||||
<svg width="14" height="14" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15.172 7l-6.586 6.586a2 2 0 102.828 2.828l6.414-6.586a4 4 0 00-5.656-5.656l-6.415 6.585a6 6 0 108.486 8.486L20.5 13"/></svg>
|
||||
Lihat Dokumen Pendukung
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
{{-- KOLOM KANAN --}}
|
||||
<div>
|
||||
<div style="position:sticky;top:1rem;">
|
||||
|
||||
{{-- Prediksi --}}
|
||||
<div class="detail-card">
|
||||
<div class="detail-card-head"><div class="detail-card-accent" style="background:#22c55e;"></div><span class="detail-card-title">Prediksi Kelayakan</span></div>
|
||||
<div class="detail-card-body">
|
||||
<div style="display:flex;align-items:center;gap:12px;margin-bottom:16px;">
|
||||
<svg width="80" height="80" viewBox="0 0 72 72">
|
||||
<circle cx="36" cy="36" r="28" fill="none" stroke="#e5e7eb" stroke-width="8"/>
|
||||
<circle id="dp-donut" cx="36" cy="36" r="28" fill="none" stroke="#d1d5db" stroke-width="8"
|
||||
stroke-dasharray="0 175.93" stroke-linecap="round"
|
||||
style="transform:rotate(-90deg);transform-origin:36px 36px;transition:stroke-dasharray .5s,stroke .3s;"/>
|
||||
<text x="36" y="40" text-anchor="middle" font-size="13" font-weight="600" fill="#111827" id="dp-donut-text">—</text>
|
||||
</svg>
|
||||
<div>
|
||||
<div class="data-lbl" style="margin-bottom:6px;">Rekomendasi</div>
|
||||
<div id="dp-reco-badge" style="display:inline-flex;padding:5px 14px;border-radius:20px;font-size:13px;font-weight:600;background:#f3f4f6;color:#374151;">—</div>
|
||||
<div class="progress-track" style="width:140px;margin-top:8px;">
|
||||
<div id="dp-prob-bar" style="height:100%;border-radius:3px;background:#d1d5db;width:0%;transition:width .5s;"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div><p class="mlbl" style="margin-bottom:5px;">Rekomendasi</p><span id="d_reco_badge" style="display:inline-flex;padding:4px 12px;border-radius:9px;font-size:12px;font-weight:700;background:#f3f4f6;color:#374151;">—</span></div>
|
||||
|
||||
{{-- Faktor Pendukung --}}
|
||||
<div style="margin-bottom:12px;">
|
||||
<div style="font-size:10px;font-weight:700;text-transform:uppercase;letter-spacing:.06em;color:#166534;margin-bottom:6px;display:flex;align-items:center;gap:5px;">
|
||||
<svg width="12" height="12" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2.5" d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z"/></svg>
|
||||
Faktor Pendukung
|
||||
</div>
|
||||
<div id="dp-positive"></div>
|
||||
</div>
|
||||
|
||||
{{-- Faktor Pengurang --}}
|
||||
<div style="margin-bottom:12px;">
|
||||
<div style="font-size:10px;font-weight:700;text-transform:uppercase;letter-spacing:.06em;color:#9f1239;margin-bottom:6px;display:flex;align-items:center;gap:5px;">
|
||||
<svg width="12" height="12" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2.5" d="M10 14l2-2m0 0l2-2m-2 2l-2-2m2 2l2 2m7-2a9 9 0 11-18 0 9 9 0 0118 0z"/></svg>
|
||||
Faktor Pengurang
|
||||
</div>
|
||||
<div id="dp-negative"></div>
|
||||
</div>
|
||||
|
||||
{{-- Penjelasan --}}
|
||||
<div style="margin-bottom:12px;">
|
||||
<div class="data-lbl" style="margin-bottom:6px;">Penjelasan Lengkap</div>
|
||||
<div style="border:1px solid #f1f5f9;border-radius:10px;overflow:hidden;">
|
||||
@php
|
||||
$penRows = [
|
||||
'dp-pen-pekerjaan' => 'Pekerjaan',
|
||||
'dp-pen-penghasilan' => 'Penghasilan',
|
||||
'dp-pen-tanggungan' => 'Tanggungan',
|
||||
'dp-pen-aset' => 'Aset',
|
||||
'dp-pen-bantuan' => 'Bantuan Lain',
|
||||
'dp-pen-usia' => 'Usia',
|
||||
'dp-pen-kondisi' => 'Kondisi Rumah',
|
||||
'dp-pen-meteran' => 'Meteran Listrik',
|
||||
'dp-pen-air' => 'Sumber Air',
|
||||
];
|
||||
@endphp
|
||||
@foreach($penRows as $pid => $plbl)
|
||||
<div class="penjelasan-row" style="padding:6px 10px;">
|
||||
<span style="font-size:12px;color:#9ca3af;">{{ $plbl }}</span>
|
||||
<span id="{{ $pid }}" style="font-size:12px;color:#111827;font-weight:500;text-align:right;margin-left:8px;">—</span>
|
||||
</div>
|
||||
@endforeach
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{-- Ringkasan --}}
|
||||
<div class="ringkasan-box">
|
||||
<div style="font-size:10px;font-weight:700;text-transform:uppercase;letter-spacing:.06em;color:#0369a1;margin-bottom:6px;">Ringkasan</div>
|
||||
<p id="dp-summary" style="font-size:12px;color:#0c4a6e;line-height:1.6;margin:0;">—</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{-- Penjelasan --}}
|
||||
<div class="msec">
|
||||
<div class="msec-h"><div class="bar" style="background:#10b981;"></div><h4>Penjelasan Prediksi</h4></div>
|
||||
<div class="msec-b" style="display:flex;flex-direction:column;gap:10px;">
|
||||
<div>
|
||||
<p class="mlbl" style="margin-bottom:4px;">Faktor Pendukung</p>
|
||||
<ul id="d_positive" style="list-style:none;padding:0;margin:0;font-size:12px;color:#166534;display:flex;flex-direction:column;gap:2px;"><li>-</li></ul>
|
||||
</div>
|
||||
<div>
|
||||
<p class="mlbl" style="margin-bottom:4px;">Faktor Pengurang</p>
|
||||
<ul id="d_negative" style="list-style:none;padding:0;margin:0;font-size:12px;color:#9f1239;display:flex;flex-direction:column;gap:2px;"><li>-</li></ul>
|
||||
</div>
|
||||
<div>
|
||||
<p class="mlbl" style="margin-bottom:4px;">Ringkasan</p>
|
||||
<p id="d_summary" style="font-size:12px;color:#374151;line-height:1.7;">-</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div style="display:flex;justify-content:space-between;flex-wrap:wrap;gap:4px;font-size:11px;color:#9ca3af;padding:0 2px;">
|
||||
<span>Input oleh: <strong id="d_input_oleh" style="color:#374151;">-</strong></span>
|
||||
<span>Didaftarkan: <strong id="d_created" style="color:#374151;">-</strong></span>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<div class="m-footer">
|
||||
<button onclick="closeDetailModal()" class="btn-close-m">Tutup</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{-- LIGHTBOX --}}
|
||||
<div id="dw-lightbox" onclick="this.style.display='none'">
|
||||
<img id="dw-lightbox-img" src="" alt="Foto">
|
||||
</div>
|
||||
|
||||
<script>
|
||||
function set(id,val){const e=document.getElementById(id);if(e)e.textContent=val;}
|
||||
function setList(id,items,empty='-'){
|
||||
const e=document.getElementById(id);if(!e)return;
|
||||
if(!items||!items.length){e.innerHTML=`<li>${empty}</li>`;return;}
|
||||
e.innerHTML=items.map(i=>`<li style="display:flex;gap:5px;"><span>•</span><span>${i}</span></li>`).join('');
|
||||
const fotoLabels = {
|
||||
foto_rumah_depan: 'Rumah Depan',
|
||||
foto_rumah_belakang: 'Rumah Belakang',
|
||||
foto_rumah_kanan: 'Rumah Kanan',
|
||||
foto_rumah_kiri: 'Rumah Kiri',
|
||||
foto_kk: 'Foto KK',
|
||||
foto_ktp: 'Foto KTP',
|
||||
foto_rekening_listrik:'Rekening Listrik',
|
||||
foto_meteran_air: 'Meteran Air',
|
||||
};
|
||||
|
||||
function s(id, val) {
|
||||
const e = document.getElementById(id);
|
||||
if (e) e.textContent = val ?? '—';
|
||||
}
|
||||
|
||||
function openDetailModal(id){
|
||||
const el=document.getElementById('warga-'+id);if(!el)return;
|
||||
const d=JSON.parse(el.textContent);
|
||||
function openDetail(id) {
|
||||
const el = document.getElementById('warga-' + id);
|
||||
if (!el) return;
|
||||
const d = JSON.parse(el.textContent);
|
||||
|
||||
set('modal-initial',(d.nama_lengkap||'?').charAt(0).toUpperCase());
|
||||
set('modal-title',d.nama_lengkap||'-');
|
||||
set('modal-subtitle',(d.pekerjaan?d.pekerjaan+' · ':'')+( d.dusun||'-'));
|
||||
// Hero
|
||||
document.getElementById('dp-avatar').textContent = (d.nama_lengkap || '?').charAt(0).toUpperCase();
|
||||
s('dp-nama', d.nama_lengkap);
|
||||
s('dp-nik', 'NIK: ' + (d.nik || '—'));
|
||||
|
||||
const dot=document.getElementById('modal-status-dot');
|
||||
const txt=document.getElementById('modal-status-text');
|
||||
const st=d.status_verifikasi||'';
|
||||
const dotC={pending:'#fcd34d',disetujui:'#6ee7b7'};
|
||||
if(dot) dot.style.cssText=`width:6px;height:6px;border-radius:50%;flex-shrink:0;background:${dotC[st]||'#fca5a5'};`;
|
||||
if(txt) txt.textContent=st==='pending'?'Menunggu Hasil Filterisasi':st==='disetujui'?'Diterima':'Tidak Diterima';
|
||||
const stMap = {
|
||||
pending: ['#fbbf24', 'Menunggu Filterisasi'],
|
||||
disetujui: ['#22c55e', 'Diterima'],
|
||||
ditolak: ['#f43f5e', 'Ditolak'],
|
||||
};
|
||||
const [stColor, stLabel] = stMap[d.status_verifikasi] ?? ['#9ca3af', d.status_verifikasi || '—'];
|
||||
const pill = document.getElementById('dp-status-pill');
|
||||
if (pill) { pill.textContent = stLabel; pill.style.borderColor = stColor + '66'; }
|
||||
|
||||
set('d_nik',d.nik||'-');set('d_no_kk',d.no_kk||'-');
|
||||
set('d_nama',d.nama_lengkap||'-');set('d_jk',d.jenis_kelamin||'-');
|
||||
set('d_lahir',(d.tempat_lahir||'-')+', '+(d.tanggal_lahir||'-'));
|
||||
set('d_usia',d.usia?d.usia+' tahun':'-');set('d_kawin',d.status_perkawinan||'-');
|
||||
set('d_alamat',d.alamat||'-');set('d_dusun',d.dusun||'-');
|
||||
set('d_rt','RT '+String(d.rt||'-').padStart(3,'0'));
|
||||
set('d_aset',d.aset_kepemilikan||'-');
|
||||
set('d_kondisi_rumah',d.kondisi_rumah||'-');
|
||||
set('d_meteran_listrik',d.meteran_listrik||'-');
|
||||
set('d_sumber_air',d.sumber_air||'-');
|
||||
set('d_pekerjaan',d.pekerjaan||'-');
|
||||
set('d_penghasilan','Rp '+Number(d.penghasilan||0).toLocaleString('id-ID'));
|
||||
set('d_tanggungan',d.jumlah_tanggungan!=null?d.jumlah_tanggungan+' orang':'-');
|
||||
set('d_bantuan',d.bantuan_lain||'-');
|
||||
// Probabilitas
|
||||
const prob = d.probability;
|
||||
const pct = prob != null ? (prob <= 1 ? prob * 100 : Number(prob)) : null;
|
||||
const color = pct != null ? (pct >= 70 ? '#10b981' : (pct >= 40 ? '#f59e0b' : '#f43f5e')) : '#d1d5db';
|
||||
const circ = 175.93;
|
||||
|
||||
const prob=d.probability;
|
||||
const emEl=document.getElementById('d_pred_empty');
|
||||
const wrEl=document.getElementById('d_pred_wrap');
|
||||
if(prob===null||prob===undefined){
|
||||
if(emEl)emEl.style.display='block';
|
||||
if(wrEl)wrEl.style.display='none';
|
||||
} else {
|
||||
if(emEl)emEl.style.display='none';
|
||||
if(wrEl)wrEl.style.display='block';
|
||||
const pct=prob<=1?prob*100:Number(prob);
|
||||
set('d_prob',pct.toFixed(1)+'%');
|
||||
const bar=document.getElementById('d_prob_bar');
|
||||
if(bar){bar.style.width=Math.min(pct,100)+'%';bar.style.background=pct>=70?'#10b981':pct>=40?'#f59e0b':'#f43f5e';}
|
||||
const rb=document.getElementById('d_reco_badge');
|
||||
if(rb){
|
||||
const r=d.recommendation||'—';
|
||||
rb.textContent=r;
|
||||
const l=r.toLowerCase().includes('layak');
|
||||
rb.style.cssText=`display:inline-flex;padding:4px 12px;border-radius:9px;font-size:12px;font-weight:700;${l?'background:#f0fdf4;color:#166534;':'background:#fff1f2;color:#9f1239;'}`;
|
||||
s('dp-prob-big', pct != null ? pct.toFixed(1) + '%' : '—');
|
||||
|
||||
const donut = document.getElementById('dp-donut');
|
||||
const donutTxt = document.getElementById('dp-donut-text');
|
||||
if (donut) {
|
||||
donut.style.stroke = color;
|
||||
donut.setAttribute('stroke-dasharray', pct != null ? `${(circ * pct / 100).toFixed(2)} ${circ}` : `0 ${circ}`);
|
||||
}
|
||||
if (donutTxt) donutTxt.textContent = pct != null ? pct.toFixed(1) + '%' : '—';
|
||||
|
||||
const probBar = document.getElementById('dp-prob-bar');
|
||||
if (probBar) { probBar.style.width = (pct ?? 0) + '%'; probBar.style.background = color; }
|
||||
|
||||
const reco = d.recommendation || '—';
|
||||
const isLayak = reco.toLowerCase().includes('layak') && !reco.toLowerCase().includes('tidak');
|
||||
const recoStyle = isLayak
|
||||
? 'background:#dcfce7;color:#15803d;'
|
||||
: (reco === '—' ? 'background:#f3f4f6;color:#374151;' : 'background:#fee2e2;color:#b91c1c;');
|
||||
|
||||
['dp-reco-badge','dp-reco-hero'].forEach(rid => {
|
||||
const rb = document.getElementById(rid);
|
||||
if (rb) {
|
||||
rb.style.cssText = `display:inline-flex;align-items:center;gap:5px;padding:5px 14px;border-radius:20px;font-size:13px;font-weight:600;${recoStyle}`;
|
||||
rb.textContent = reco;
|
||||
}
|
||||
});
|
||||
|
||||
// Identitas
|
||||
s('dp-no_kk', d.no_kk);
|
||||
s('dp-jk', d.jenis_kelamin);
|
||||
s('dp-tempat_lahir', d.tempat_lahir);
|
||||
s('dp-tanggal_lahir', d.tanggal_lahir);
|
||||
s('dp-usia', d.usia ? d.usia + ' tahun' : '—');
|
||||
s('dp-kawin', d.status_perkawinan);
|
||||
s('dp-input_oleh', d.input_oleh);
|
||||
s('dp-created_at', d.created_at);
|
||||
|
||||
// Tempat Tinggal
|
||||
s('dp-alamat', d.alamat);
|
||||
s('dp-dusun', d.dusun);
|
||||
s('dp-rt', 'RT ' + String(d.rt || '—').padStart(3, '0'));
|
||||
s('dp-aset', d.aset_kepemilikan);
|
||||
s('dp-lantai', d.lantai_rumah);
|
||||
s('dp-dinding', d.dinding_rumah);
|
||||
s('dp-atap', d.atap_rumah);
|
||||
s('dp-luas', d.luas_rumah_m2 ? d.luas_rumah_m2 + ' m²' : '—');
|
||||
s('dp-status_rumah', d.status_kepemilikan_rumah);
|
||||
s('dp-meteran', d.meteran_listrik);
|
||||
s('dp-air', d.sumber_air);
|
||||
|
||||
// Ekonomi
|
||||
s('dp-pekerjaan', d.pekerjaan);
|
||||
s('dp-penghasilan', 'Rp ' + Number(d.penghasilan || 0).toLocaleString('id-ID'));
|
||||
s('dp-tanggungan', d.jumlah_tanggungan != null ? d.jumlah_tanggungan + ' orang' : '—');
|
||||
s('dp-bantuan', d.bantuan_lain);
|
||||
|
||||
// Penjelasan rows
|
||||
s('dp-pen-pekerjaan', d.pekerjaan);
|
||||
s('dp-pen-penghasilan', 'Rp ' + Number(d.penghasilan || 0).toLocaleString('id-ID'));
|
||||
s('dp-pen-tanggungan', d.jumlah_tanggungan != null ? d.jumlah_tanggungan + ' orang' : '—');
|
||||
s('dp-pen-aset', d.aset_kepemilikan);
|
||||
s('dp-pen-bantuan', d.bantuan_lain);
|
||||
s('dp-pen-usia', d.usia ? d.usia + ' tahun' : '—');
|
||||
s('dp-pen-kondisi', d.kondisi_rumah);
|
||||
s('dp-pen-meteran', d.meteran_listrik);
|
||||
s('dp-pen-air', d.sumber_air);
|
||||
|
||||
// Faktor
|
||||
const posEl = document.getElementById('dp-positive');
|
||||
const negEl = document.getElementById('dp-negative');
|
||||
if (posEl) posEl.innerHTML = (d.positive || []).length
|
||||
? d.positive.map(f => `<div class="faktor-item faktor-pos"><svg width="12" height="12" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2.5" d="M5 13l4 4L19 7"/></svg>${f}</div>`).join('')
|
||||
: '<div style="font-size:12px;color:#9ca3af;padding:4px 0;">Tidak ada faktor pendukung dominan.</div>';
|
||||
if (negEl) negEl.innerHTML = (d.negative || []).length
|
||||
? d.negative.map(f => `<div class="faktor-item faktor-neg"><svg width="12" height="12" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2.5" d="M6 18L18 6M6 6l12 12"/></svg>${f}</div>`).join('')
|
||||
: '<div style="font-size:12px;color:#9ca3af;padding:4px 0;">Tidak ada faktor pengurang dominan.</div>';
|
||||
|
||||
s('dp-summary', d.summary);
|
||||
|
||||
// Foto
|
||||
const fotoGrid = document.getElementById('dp-foto-grid');
|
||||
if (fotoGrid) {
|
||||
fotoGrid.innerHTML = Object.entries(fotoLabels).map(([key, label]) => {
|
||||
const url = d[key];
|
||||
return `<div class="foto-thumb" ${url ? `onclick="openLightbox('${url}')"` : ''}>
|
||||
${url
|
||||
? `<img src="${url}" alt="${label}" loading="lazy">`
|
||||
: `<div class="foto-placeholder">
|
||||
<svg width="20" height="20" fill="none" stroke="#d1d5db" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" d="M4 16l4.586-4.586a2 2 0 012.828 0L16 16m-2-2l1.586-1.586a2 2 0 012.828 0L20 14m-6-6h.01M6 20h12a2 2 0 002-2V6a2 2 0 00-2-2H6a2 2 0 00-2 2v12a2 2 0 002 2z"/></svg>
|
||||
<span style="font-size:9px;color:#9ca3af;">Belum ada</span>
|
||||
</div>`
|
||||
}
|
||||
<div class="foto-label">${label}</div>
|
||||
</div>`;
|
||||
}).join('');
|
||||
}
|
||||
|
||||
setList('d_positive',d.positive||[],'Tidak ada faktor pendukung dominan.');
|
||||
setList('d_negative',d.negative||[],'Tidak ada faktor pengurang dominan.');
|
||||
set('d_summary',d.summary||'-');
|
||||
set('d_input_oleh',d.input_oleh||'-');
|
||||
set('d_created',d.created_at||'-');
|
||||
const dokWrap = document.getElementById('dp-dokumen-wrap');
|
||||
const dokLink = document.getElementById('dp-dokumen-link');
|
||||
if (dokWrap && dokLink) {
|
||||
if (d.dokumen_pendukung) { dokLink.href = d.dokumen_pendukung; dokWrap.style.display = 'block'; }
|
||||
else dokWrap.style.display = 'none';
|
||||
}
|
||||
|
||||
document.getElementById('detailBackdrop').style.display='block';
|
||||
document.getElementById('detailModal').style.display='flex';
|
||||
document.body.style.overflow='hidden';
|
||||
// Tampilkan panel, sembunyikan tabel
|
||||
document.getElementById('tableCard').style.display = 'none';
|
||||
document.getElementById('detailPanel').style.display = 'block';
|
||||
window.scrollTo({ top: 0, behavior: 'smooth' });
|
||||
}
|
||||
|
||||
function closeDetailModal(){
|
||||
document.getElementById('detailBackdrop').style.display='none';
|
||||
document.getElementById('detailModal').style.display='none';
|
||||
document.body.style.overflow='';
|
||||
function closeDetail() {
|
||||
document.getElementById('detailPanel').style.display = 'none';
|
||||
document.getElementById('tableCard').style.display = 'block';
|
||||
window.scrollTo({ top: 0, behavior: 'smooth' });
|
||||
}
|
||||
|
||||
document.addEventListener('keydown',e=>{ if(e.key==='Escape') closeDetailModal(); });
|
||||
function openLightbox(src) {
|
||||
const lb = document.getElementById('dw-lightbox');
|
||||
document.getElementById('dw-lightbox-img').src = src;
|
||||
lb.style.display = 'flex';
|
||||
}
|
||||
|
||||
document.addEventListener('keydown', e => { if (e.key === 'Escape') document.getElementById('dw-lightbox').style.display = 'none'; });
|
||||
</script>
|
||||
|
||||
</x-app-layout>
|
||||
|
|
@ -13,20 +13,14 @@
|
|||
.fi-card{background:#fff;border-radius:16px;border:1.5px solid #f1f5f9;box-shadow:0 1px 4px rgba(0,0,0,.04);overflow:hidden;}
|
||||
.fl-box{display:flex;align-items:center;gap:9px;border-radius:12px;padding:10px 14px;font-size:12px;font-weight:500;margin-bottom:10px;}
|
||||
.fl-box svg{width:14px;height:14px;flex-shrink:0;}
|
||||
|
||||
/* form controls */
|
||||
.fi-label{display:block;font-size:10.5px;font-weight:600;color:#64748b;margin-bottom:5px;}
|
||||
.fi-select,.fi-input{width:100%;padding:9px 12px;font-size:13px;color:#111827;background:#f9fafb;border:1.5px solid #e5e7eb;border-radius:11px;outline:none;font-family:inherit;transition:border-color .15s;}
|
||||
.fi-select:focus,.fi-input:focus{border-color:#3b82f6;background:#fff;}
|
||||
|
||||
/* buttons */
|
||||
.btn-fi{display:inline-flex;align-items:center;gap:6px;padding:9px 18px;font-size:12.5px;font-weight:700;border:none;border-radius:11px;cursor:pointer;white-space:nowrap;font-family:inherit;transition:filter .1s;}
|
||||
.btn-fi:hover{filter:brightness(.93);}
|
||||
.btn-tampil{background:#2563eb;color:#fff;box-shadow:0 2px 8px rgba(37,99,235,.2);}
|
||||
.btn-reset-fi{background:#f1f5f9;color:#475569;}
|
||||
.btn-tetapkan{background:#059669;color:#fff;box-shadow:0 2px 8px rgba(5,150,105,.2);}
|
||||
|
||||
/* table */
|
||||
.tbl-wrap{overflow-x:auto;-webkit-overflow-scrolling:touch;}
|
||||
table{width:100%;border-collapse:collapse;min-width:580px;}
|
||||
thead tr{background:#f8fafc;border-bottom:1.5px solid #f1f5f9;}
|
||||
|
|
@ -35,29 +29,18 @@
|
|||
tbody tr:hover{background:#f0f7ff;}
|
||||
tbody tr:last-child{border-bottom:none;}
|
||||
tbody td{padding:10px 12px;vertical-align:middle;font-size:12.5px;}
|
||||
|
||||
.badge{display:inline-flex;align-items:center;gap:4px;padding:3px 9px;border-radius:20px;font-size:10.5px;font-weight:700;white-space:nowrap;}
|
||||
.dot{width:5px;height:5px;border-radius:50%;flex-shrink:0;}
|
||||
|
||||
.prob-wrap{display:flex;align-items:center;gap:6px;}
|
||||
.prob-bg{flex:1;height:5px;background:#f1f5f9;border-radius:99px;overflow:hidden;min-width:50px;}
|
||||
.prob-bar{height:100%;border-radius:99px;}
|
||||
|
||||
/* ranking badge */
|
||||
.rank-badge{width:26px;height:26px;border-radius:8px;display:flex;align-items:center;justify-content:center;font-size:11px;font-weight:800;}
|
||||
|
||||
/* note box */
|
||||
.note-box{background:#fffbeb;border:1.5px solid #fde68a;border-radius:14px;padding:14px 16px;font-size:12px;color:#92400e;line-height:1.8;}
|
||||
.note-box b{font-weight:800;}
|
||||
|
||||
/* filter form grid */
|
||||
.fi-grid{display:grid;grid-template-columns:1fr 1fr auto;gap:10px;align-items:end;}
|
||||
.fi-actions{display:flex;gap:8px;flex-wrap:wrap;align-items:center;}
|
||||
|
||||
/* mobile card */
|
||||
.mob-fi{display:none;flex-direction:column;gap:8px;padding:12px 14px;}
|
||||
.mob-fi-row{background:#f8fafc;border:1.5px solid #f1f5f9;border-radius:12px;padding:11px 13px;}
|
||||
|
||||
@media(max-width:768px){
|
||||
.fi-grid{grid-template-columns:1fr 1fr;gap:8px;}
|
||||
.fi-grid > *:last-child{grid-column:span 2;}
|
||||
|
|
@ -68,8 +51,83 @@
|
|||
.tbl-wrap table{display:none;}
|
||||
.mob-fi{display:flex;}
|
||||
}
|
||||
|
||||
@keyframes fi-spin { to { transform: rotate(360deg); } }
|
||||
.fi-spinner {
|
||||
display:none;
|
||||
width:13px;height:13px;
|
||||
border:2px solid rgba(255,255,255,.35);
|
||||
border-top-color:#fff;
|
||||
border-radius:50%;
|
||||
animation:fi-spin .6s linear infinite;
|
||||
flex-shrink:0;
|
||||
}
|
||||
.fi-spinner-dark {
|
||||
border-color:rgba(71,85,105,.3);
|
||||
border-top-color:#475569;
|
||||
}
|
||||
|
||||
/* Overlay loading saat proses tetapkan */
|
||||
#fi-overlay {
|
||||
display:none;
|
||||
position:fixed;inset:0;z-index:9999;
|
||||
background:rgba(15,23,42,.45);
|
||||
backdrop-filter:blur(2px);
|
||||
align-items:center;justify-content:center;
|
||||
}
|
||||
#fi-overlay.show { display:flex; }
|
||||
.fi-overlay-box {
|
||||
background:#fff;border-radius:20px;
|
||||
padding:28px 32px;
|
||||
display:flex;flex-direction:column;align-items:center;gap:16px;
|
||||
box-shadow:0 20px 60px rgba(0,0,0,.2);
|
||||
min-width:240px;text-align:center;
|
||||
}
|
||||
.fi-overlay-ring {
|
||||
width:48px;height:48px;
|
||||
border:4px solid #e5e7eb;
|
||||
border-top-color:#059669;
|
||||
border-radius:50%;
|
||||
animation:fi-spin .7s linear infinite;
|
||||
}
|
||||
.fi-overlay-ring.blue { border-top-color:#2563eb; }
|
||||
.fi-overlay-title { font-size:14px;font-weight:700;color:#0f172a; }
|
||||
.fi-overlay-sub { font-size:11.5px;color:#94a3b8;margin-top:-8px; }
|
||||
|
||||
/* Dana Cair Box */
|
||||
.dana-box{background:linear-gradient(135deg,#eff6ff,#dbeafe);border:1.5px solid #bfdbfe;border-radius:14px;padding:14px 16px;margin-bottom:12px;}
|
||||
.dana-grid{display:grid;grid-template-columns:1fr 1fr;gap:12px;align-items:end;}
|
||||
.dana-stat-row{display:grid;grid-template-columns:1fr 1fr 1fr;gap:10px;margin-top:12px;}
|
||||
.dana-stat{background:#fff;border-radius:10px;padding:10px 13px;border:1.5px solid #e0eaff;}
|
||||
.dana-stat.green{border-color:#a7f3d0;background:#f0fdf4;}
|
||||
.dana-stat.red{border-color:#fecdd3;background:#fff1f2;}
|
||||
.dana-stat.amber{border-color:#fde68a;background:#fffbeb;}
|
||||
.dana-stat-label{font-size:9.5px;font-weight:700;color:#64748b;text-transform:uppercase;letter-spacing:.06em;margin-bottom:3px;}
|
||||
.dana-stat-val{font-size:15px;font-weight:800;color:#1e40af;}
|
||||
.dana-stat-val.green{color:#059669;}
|
||||
.dana-stat-val.red{color:#e11d48;}
|
||||
.dana-stat-val.amber{color:#b45309;}
|
||||
.dana-stat-sub{font-size:9.5px;color:#94a3b8;margin-top:1px;}
|
||||
.kuota-rec{background:#fff;border:1.5px solid #a7f3d0;border-radius:10px;padding:8px 12px;font-size:11.5px;color:#065f46;font-weight:600;display:flex;align-items:center;gap:6px;}
|
||||
.periode-next{background:#fffbeb;border:1.5px solid #fde68a;border-radius:10px;padding:10px 13px;margin-top:10px;font-size:11.5px;color:#92400e;display:none;line-height:1.7;}
|
||||
.periode-next b{font-weight:800;}
|
||||
@media(max-width:640px){.dana-grid{grid-template-columns:1fr;}.dana-stat-row{grid-template-columns:1fr 1fr;}.dana-stat-row>*:last-child{grid-column:span 2;}}
|
||||
@media(max-width:420px){.dana-stat-row{grid-template-columns:1fr;}}
|
||||
|
||||
/* Row warga sudah terpilih (dikunci) */
|
||||
tr.row-locked { opacity:.55; }
|
||||
tr.row-locked td { pointer-events:none; }
|
||||
</style>
|
||||
|
||||
{{-- OVERLAY LOADING --}}
|
||||
<div id="fi-overlay">
|
||||
<div class="fi-overlay-box">
|
||||
<div class="fi-overlay-ring" id="fi-overlay-ring"></div>
|
||||
<div class="fi-overlay-title" id="fi-overlay-title">Memuat data...</div>
|
||||
<div class="fi-overlay-sub" id="fi-overlay-sub">Mohon tunggu sebentar</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{-- FLASH --}}
|
||||
@if(session('success'))
|
||||
<div class="fl-box" style="background:#f0fdf4;border:1.5px solid #bbf7d0;color:#166534;">
|
||||
|
|
@ -84,6 +142,150 @@
|
|||
</div>
|
||||
@endif
|
||||
|
||||
{{-- ══════════════════════════════════════════════════════
|
||||
PANEL DANA & PERIODE BLT-DD
|
||||
══════════════════════════════════════════════════════ --}}
|
||||
<div class="dana-box" style="margin-bottom:12px;">
|
||||
<div style="display:flex;align-items:center;gap:7px;margin-bottom:11px;">
|
||||
<div style="width:3px;height:14px;background:#2563eb;border-radius:4px;"></div>
|
||||
<h3 style="font-size:12px;font-weight:700;color:#1e40af;">Kalkulator Dana BLT-DD</h3>
|
||||
<span style="margin-left:auto;font-size:10px;font-weight:600;color:#3b82f6;background:#dbeafe;padding:2px 8px;border-radius:20px;">Rp 300.000 / orang / bulan</span>
|
||||
</div>
|
||||
|
||||
<div class="dana-grid">
|
||||
<div>
|
||||
<label class="fi-label" style="color:#1e40af;">Total Dana Cair (Rp)</label>
|
||||
<input type="text" id="inputDana" inputmode="numeric"
|
||||
placeholder="Contoh: 300.000.000"
|
||||
value="{{ $danaSisaTerakhir ? number_format($danaSisaTerakhir,0,',','.') : '' }}"
|
||||
class="fi-input"
|
||||
style="border-color:#bfdbfe;background:#fff;"
|
||||
oninput="formatDana(this)">
|
||||
@if($danaSisaTerakhir)
|
||||
<div style="font-size:9.5px;color:#059669;margin-top:3px;font-weight:600;">
|
||||
Otomatis diisi dari sisa periode {{ $periodeTerakhir->periode }}
|
||||
</div>
|
||||
@else
|
||||
<div style="font-size:9.5px;color:#64748b;margin-top:3px;">Masukkan total dana yang dicairkan pemerintah</div>
|
||||
@endif
|
||||
</div>
|
||||
<div>
|
||||
<label class="fi-label" style="color:#1e40af;">Penerima Bulan Ini (orang)</label>
|
||||
<div id="penerimaOtomatis" class="fi-input" style="border-color:#bfdbfe;background:#f0f9ff;display:flex;align-items:center;font-weight:800;color:#1e40af;font-size:16px;">—</div>
|
||||
<div style="font-size:9.5px;color:#64748b;margin-top:3px;" id="penerimaOtomatisSub">Otomatis: Dana ÷ {{ max(1, 13 - $nextPeriodeNum) }} bulan ÷ Rp 300.000</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{-- Stat row --}}
|
||||
<div class="dana-stat-row" id="danaStatRow" style="display:none;">
|
||||
<div class="dana-stat red">
|
||||
<div class="dana-stat-label">Dana Terpakai</div>
|
||||
<div class="dana-stat-val red" id="danaTermakai">—</div>
|
||||
<div class="dana-stat-sub" id="danaTermakaiSub">—</div>
|
||||
</div>
|
||||
<div class="dana-stat green">
|
||||
<div class="dana-stat-label">Sisa Dana</div>
|
||||
<div class="dana-stat-val green" id="danaSisa">—</div>
|
||||
<div class="dana-stat-sub" id="danaSisaSub">—</div>
|
||||
</div>
|
||||
<div class="dana-stat amber">
|
||||
<div class="dana-stat-label">Sisa untuk Periode Berikutnya</div>
|
||||
<div class="dana-stat-val amber" id="estimasiPeriode">—</div>
|
||||
<div class="dana-stat-sub" id="estimasiPeriodeSub">—</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{-- Form simpan periode --}}
|
||||
<div id="formSimpanWrap" style="display:none;margin-top:12px;background:#fff;border:1.5px solid #bfdbfe;border-radius:12px;padding:12px 14px;">
|
||||
<div style="font-size:11px;font-weight:700;color:#1e40af;margin-bottom:9px;">Simpan ke Riwayat Periode</div>
|
||||
<form method="POST" action="{{ route('admin.filterisasi.simpanPeriode') }}"
|
||||
style="display:grid;grid-template-columns:1fr 1fr auto;gap:9px;align-items:end;">
|
||||
@csrf
|
||||
<input type="hidden" name="dana_awal" id="hiddenDanaAwal">
|
||||
<input type="hidden" name="jumlah_penerima" id="hiddenPenerima">
|
||||
<div>
|
||||
<label class="fi-label">Nama Periode</label>
|
||||
<input type="text" name="periode" value="Bulan {{ $nextPeriodeNum }}"
|
||||
class="fi-input" style="font-size:12px;" placeholder="Misal: Bulan 1">
|
||||
</div>
|
||||
<div>
|
||||
<label class="fi-label">Keterangan (opsional)</label>
|
||||
<input type="text" name="keterangan" class="fi-input" style="font-size:12px;"
|
||||
placeholder="Misal: Triwulan I 2026">
|
||||
</div>
|
||||
<button type="submit" class="btn-fi btn-tetapkan" style="padding:9px 14px;"
|
||||
onclick="return syncHidden()">
|
||||
<svg width="13" height="13" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 7H5a2 2 0 00-2 2v9a2 2 0 002 2h14a2 2 0 002-2V9a2 2 0 00-2-2h-3m-1 4l-3 3m0 0l-3-3m3 3V4"/></svg>
|
||||
Simpan
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
{{-- Rekomendasi per dusun --}}
|
||||
<div id="rekomendasiBox" style="display:none;margin-top:12px;">
|
||||
<div style="font-size:10.5px;font-weight:700;color:#1e40af;margin-bottom:7px;">
|
||||
Rekomendasi Kuota per Dusun (proporsional berdasarkan jumlah kandidat)
|
||||
</div>
|
||||
<div id="rekomendasiList" style="display:flex;flex-wrap:wrap;gap:7px;"></div>
|
||||
<div style="font-size:9.5px;color:#64748b;margin-top:7px;">* Klik rekomendasi dusun untuk langsung isi kuota di form filter bawah</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{-- RIWAYAT PERIODE DANA --}}
|
||||
@if($periodeDana->isNotEmpty())
|
||||
<div class="fi-card" style="margin-bottom:12px;">
|
||||
<div style="padding:10px 16px;border-bottom:1px solid #f1f5f9;background:#fafafa;display:flex;align-items:center;gap:7px;">
|
||||
<div style="width:3px;height:15px;background:#059669;border-radius:4px;"></div>
|
||||
<span style="font-size:12.5px;font-weight:700;color:#374151;">Riwayat Periode Dana BLT-DD</span>
|
||||
<span style="margin-left:auto;font-size:11px;color:#059669;background:#f0fdf4;padding:2px 9px;border-radius:20px;font-weight:600;border:1px solid #bbf7d0;">
|
||||
{{ $periodeDana->count() }} periode
|
||||
</span>
|
||||
</div>
|
||||
<div class="tbl-wrap">
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Periode</th>
|
||||
<th>Dana Awal</th>
|
||||
<th>Penerima</th>
|
||||
<th>Dana Terpakai</th>
|
||||
<th>Sisa Dana</th>
|
||||
<th>Keterangan</th>
|
||||
<th>Aksi</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@foreach($periodeDana as $p)
|
||||
<tr>
|
||||
<td><span style="font-size:12px;font-weight:700;color:#0f172a;">{{ $p->periode }}</span></td>
|
||||
<td style="font-size:12px;color:#374151;">Rp {{ number_format($p->dana_awal,0,',','.') }}</td>
|
||||
<td>
|
||||
<span class="badge" style="background:#dbeafe;color:#1e40af;border:1px solid #bfdbfe;">
|
||||
{{ $p->jumlah_penerima }} orang
|
||||
</span>
|
||||
</td>
|
||||
<td style="font-size:12px;color:#e11d48;font-weight:600;">Rp {{ number_format($p->dana_terpakai,0,',','.') }}</td>
|
||||
<td style="font-size:12px;color:#059669;font-weight:700;">Rp {{ number_format($p->dana_sisa,0,',','.') }}</td>
|
||||
<td style="font-size:11.5px;color:#64748b;">{{ $p->keterangan ?? '-' }}</td>
|
||||
<td>
|
||||
<form method="POST" action="{{ route('admin.filterisasi.hapusPeriode', $p->id) }}" style="display:inline;">
|
||||
@csrf
|
||||
@method('DELETE')
|
||||
<button type="submit" class="btn-fi btn-reset-fi" style="padding:5px 10px;font-size:11px;"
|
||||
onclick="return confirm('Hapus data periode ini?')">
|
||||
<svg width="11" height="11" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16"/></svg>
|
||||
Hapus
|
||||
</button>
|
||||
</form>
|
||||
</td>
|
||||
</tr>
|
||||
@endforeach
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
@endif
|
||||
|
||||
{{-- FILTER FORM --}}
|
||||
<div class="fi-card" style="padding:14px 16px;margin-bottom:12px;">
|
||||
<div style="display:flex;align-items:center;gap:7px;margin-bottom:12px;">
|
||||
|
|
@ -91,14 +293,16 @@
|
|||
<h3 style="font-size:12px;font-weight:700;color:#374151;">Pilih Dusun & Kuota</h3>
|
||||
</div>
|
||||
|
||||
<form method="GET" action="{{ route('admin.filterisasi') }}">
|
||||
<form method="GET" action="{{ route('admin.filterisasi') }}" id="formTampil">
|
||||
<div class="fi-grid">
|
||||
<div>
|
||||
<label class="fi-label">Dusun</label>
|
||||
<select name="dusun_id" class="fi-select">
|
||||
<select name="dusun_id" class="fi-select" id="selectDusun">
|
||||
<option value="0">-- pilih dusun --</option>
|
||||
@foreach($dusuns as $d)
|
||||
<option value="{{ $d->id }}" {{ (int)($dusunId??0)===$d->id?'selected':'' }}>
|
||||
<option value="{{ $d->id }}"
|
||||
data-nama="{{ $d->nama_dusun }}"
|
||||
{{ (int)($dusunId??0)===$d->id?'selected':'' }}>
|
||||
{{ $d->nama_dusun }}
|
||||
</option>
|
||||
@endforeach
|
||||
|
|
@ -107,14 +311,16 @@
|
|||
|
||||
<div>
|
||||
<label class="fi-label">Kuota per Dusun</label>
|
||||
<input type="number" name="kuota" min="1" max="100"
|
||||
<input type="number" name="kuota" id="inputKuota" min="1" max="100"
|
||||
value="{{ (int)($kuota??7) }}" class="fi-input">
|
||||
</div>
|
||||
|
||||
<div class="fi-actions">
|
||||
<button type="submit" class="btn-fi btn-tampil">
|
||||
<svg width="13" height="13" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M3 4a1 1 0 011-1h16a1 1 0 011 1v2a1 1 0 01-.293.707L13 13.414V19a1 1 0 01-.553.894l-4 2A1 1 0 017 21v-7.586L3.293 6.707A1 1 0 013 6V4z"/></svg>
|
||||
Tampilkan
|
||||
{{-- Tombol Tampilkan --}}
|
||||
<button type="submit" class="btn-fi btn-tampil" id="btnTampil">
|
||||
<span class="fi-spinner" id="spinnerTampil"></span>
|
||||
<svg id="iconTampil" width="13" height="13" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M3 4a1 1 0 011-1h16a1 1 0 011 1v2a1 1 0 01-.293.707L13 13.414V19a1 1 0 01-.553.894l-4 2A1 1 0 017 21v-7.586L3.293 6.707A1 1 0 013 6V4z"/></svg>
|
||||
<span id="textTampil">Tampilkan</span>
|
||||
</button>
|
||||
|
||||
@if(!empty($dusunId))
|
||||
|
|
@ -128,14 +334,14 @@
|
|||
</button>
|
||||
</form>
|
||||
|
||||
<form method="POST" action="{{ route('admin.filterisasi.tetapkan') }}" style="display:contents;">
|
||||
<form method="POST" action="{{ route('admin.filterisasi.tetapkan') }}" style="display:contents;" id="formTetapkan">
|
||||
@csrf
|
||||
<input type="hidden" name="dusun_id" value="{{ $dusunId }}">
|
||||
<input type="hidden" name="kuota" value="{{ (int)($kuota??7) }}">
|
||||
<button type="submit" class="btn-fi btn-tetapkan"
|
||||
onclick="return confirm('Tetapkan {{ (int)($kuota??7) }} orang dengan probabilitas tertinggi untuk dusun ini?')">
|
||||
<svg width="13" height="13" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z"/></svg>
|
||||
Tetapkan {{ (int)($kuota??7) }} Teratas
|
||||
<button type="submit" class="btn-fi btn-tetapkan" id="btnTetapkan">
|
||||
<span class="fi-spinner" id="spinnerTetapkan"></span>
|
||||
<svg id="iconTetapkan" width="13" height="13" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z"/></svg>
|
||||
<span id="textTetapkan">Tetapkan {{ (int)($kuota??7) }} Teratas</span>
|
||||
</button>
|
||||
</form>
|
||||
@endif
|
||||
|
|
@ -149,8 +355,27 @@
|
|||
<div style="padding:10px 16px;border-bottom:1px solid #f1f5f9;background:#fafafa;display:flex;align-items:center;gap:7px;">
|
||||
<div style="width:3px;height:15px;background:#2563eb;border-radius:4px;"></div>
|
||||
<span style="font-size:12.5px;font-weight:700;color:#374151;">Kandidat Sedang Divalidasi / Sudah Diproses</span>
|
||||
@if(isset($candidates) && method_exists($candidates, 'total') && $candidates->total() > 0)
|
||||
@php
|
||||
$totalKandidatAktif = $candidates->total() - (isset($pickedNiks) ? $pickedNiks->count() : 0);
|
||||
@endphp
|
||||
<span style="margin-left:auto;font-size:11px;color:#64748b;background:#f1f5f9;padding:2px 9px;border-radius:20px;font-weight:600;">
|
||||
{{ $candidates->total() }} kandidat
|
||||
@if(isset($pickedNiks) && $pickedNiks->count() > 0)
|
||||
<span style="color:#059669;">({{ $pickedNiks->count() }} sudah terpilih)</span>
|
||||
@endif
|
||||
</span>
|
||||
@endif
|
||||
</div>
|
||||
|
||||
{{-- INFO warga sudah terpilih dikunci --}}
|
||||
@if(isset($pickedNiks) && $pickedNiks->count() > 0)
|
||||
<div style="padding:8px 16px;background:#f0fdf4;border-bottom:1px solid #bbf7d0;font-size:11.5px;color:#166534;display:flex;align-items:center;gap:6px;">
|
||||
<svg width="13" height="13" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 15v2m-6 4h12a2 2 0 002-2v-6a2 2 0 00-2-2H6a2 2 0 00-2 2v6a2 2 0 002 2zm10-10V7a4 4 0 00-8 0v4h8z"/></svg>
|
||||
<span><b>{{ $pickedNiks->count() }} warga</b> sudah ditetapkan sebagai penerima — tidak ikut seleksi ulang (ditampilkan redup)</span>
|
||||
</div>
|
||||
@endif
|
||||
|
||||
{{-- DESKTOP TABLE --}}
|
||||
<div class="tbl-wrap">
|
||||
<table>
|
||||
|
|
@ -166,85 +391,7 @@
|
|||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@forelse(($candidates??[]) as $index => $w)
|
||||
@php
|
||||
$prob = $w->prob ?? optional($w->prediksiKelayakan)->probability;
|
||||
$probPct = $prob !== null ? ($prob<=1 ? $prob*100 : $prob) : null;
|
||||
$sc = $probPct !== null ? ($probPct>=70?'#10b981':($probPct>=40?'#f59e0b':'#f43f5e')) : '#e5e7eb';
|
||||
$picked = isset($pickedIds) ? $pickedIds->contains($w->id) : false;
|
||||
$rank = (($candidates->currentPage()-1)*$candidates->perPage())+$index+1;
|
||||
@endphp
|
||||
<tr style="{{ $picked ? 'background:#f0fdf4;' : '' }}">
|
||||
|
||||
{{-- Rank --}}
|
||||
<td>
|
||||
@php
|
||||
$rk = ['linear-gradient(135deg,#f59e0b,#d97706)','linear-gradient(135deg,#94a3b8,#64748b)','linear-gradient(135deg,#f97316,#ea580c)'];
|
||||
$rbg = $rank<=3 ? $rk[$rank-1] : '#f1f5f9';
|
||||
$rtc = $rank<=3 ? '#fff' : '#64748b';
|
||||
@endphp
|
||||
<div class="rank-badge" style="background:{{ $rbg }};color:{{ $rtc }};">{{ $rank }}</div>
|
||||
</td>
|
||||
|
||||
{{-- Nama --}}
|
||||
<td>
|
||||
<div style="display:flex;align-items:center;gap:8px;">
|
||||
<div style="width:28px;height:28px;border-radius:8px;background:linear-gradient(135deg,#3b82f6,#2563eb);display:flex;align-items:center;justify-content:center;font-size:11px;font-weight:800;color:#fff;flex-shrink:0;">
|
||||
{{ strtoupper(substr($w->nama_lengkap,0,1)) }}
|
||||
</div>
|
||||
<span style="font-size:12px;font-weight:600;color:#0f172a;">{{ $w->nama_lengkap }}</span>
|
||||
</div>
|
||||
</td>
|
||||
|
||||
{{-- NIK --}}
|
||||
<td>
|
||||
<span style="font-family:monospace;font-size:10.5px;color:#6b7280;background:#f3f4f6;padding:2px 7px;border-radius:6px;">{{ $w->nik }}</span>
|
||||
</td>
|
||||
|
||||
{{-- Dusun --}}
|
||||
<td style="font-size:12px;color:#374151;">
|
||||
{{ optional(optional($w->rt)->dusun)->nama_dusun ?? '-' }}
|
||||
</td>
|
||||
|
||||
{{-- Probabilitas --}}
|
||||
<td>
|
||||
@if($probPct !== null)
|
||||
<div class="prob-wrap">
|
||||
<div class="prob-bg">
|
||||
<div class="prob-bar" style="width:{{ min($probPct,100) }}%;background:{{ $sc }};"></div>
|
||||
</div>
|
||||
<span style="font-size:11.5px;font-weight:700;color:{{ $sc }};white-space:nowrap;">{{ number_format((float)$probPct,1) }}%</span>
|
||||
</div>
|
||||
@else
|
||||
<span style="color:#d1d5db;">—</span>
|
||||
@endif
|
||||
</td>
|
||||
|
||||
{{-- Status verifikasi --}}
|
||||
<td>
|
||||
@php
|
||||
$sv=['pending'=>['#fffbeb','#b45309','#fde68a','Pending'],'disetujui'=>['#f0fdf4','#166534','#bbf7d0','Disetujui'],'ditolak'=>['#fff1f2','#9f1239','#fecdd3','Ditolak']];
|
||||
$sv=$sv[$w->status_verifikasi??'']??['#f3f4f6','#6b7280','#e5e7eb',ucfirst($w->status_verifikasi??'-')];
|
||||
@endphp
|
||||
<span class="badge" style="background:{{ $sv[0] }};color:{{ $sv[1] }};border:1px solid {{ $sv[2] }};">
|
||||
<span class="dot" style="background:{{ $sv[1] }};"></span>{{ $sv[3] }}
|
||||
</span>
|
||||
</td>
|
||||
|
||||
{{-- Final (terpilih) --}}
|
||||
<td>
|
||||
@if($picked)
|
||||
<span class="badge" style="background:#f0fdf4;color:#166534;border:1px solid #bbf7d0;">
|
||||
<span class="dot" style="background:#10b981;"></span>Terpilih
|
||||
</span>
|
||||
@else
|
||||
<span class="badge" style="background:#f3f4f6;color:#6b7280;border:1px solid #e5e7eb;">
|
||||
<span class="dot" style="background:#9ca3af;"></span>Belum
|
||||
</span>
|
||||
@endif
|
||||
</td>
|
||||
</tr>
|
||||
@empty
|
||||
@if(!isset($dusunId) || (int)$dusunId === 0)
|
||||
<tr><td colspan="7">
|
||||
<div style="padding:48px 16px;text-align:center;">
|
||||
<div style="width:44px;height:44px;background:#f3f4f6;border-radius:50%;display:flex;align-items:center;justify-content:center;margin:0 auto 10px;">
|
||||
|
|
@ -253,72 +400,149 @@
|
|||
<p style="font-size:13px;font-weight:600;color:#6b7280;">Pilih dusun untuk menampilkan kandidat</p>
|
||||
</div>
|
||||
</td></tr>
|
||||
@endforelse
|
||||
@elseif($candidates->isEmpty())
|
||||
<tr><td colspan="7">
|
||||
<div style="padding:48px 16px;text-align:center;">
|
||||
<div style="width:44px;height:44px;background:#fff7ed;border-radius:50%;display:flex;align-items:center;justify-content:middle;margin:0 auto 10px;border:1.5px solid #fed7aa;">
|
||||
<svg width="22" height="22" fill="none" stroke="#f97316" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" d="M12 9v2m0 4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"/></svg>
|
||||
</div>
|
||||
<p style="font-size:13px;font-weight:600;color:#6b7280;">Tidak ada kandidat dengan status <strong>sedang_validasi</strong> atau <strong>selesai</strong> di dusun ini</p>
|
||||
</div>
|
||||
</td></tr>
|
||||
@else
|
||||
@foreach($candidates as $index => $w)
|
||||
@php
|
||||
$prob = $w->prob ?? optional($w->prediksiKelayakan)->probability;
|
||||
$probPct = $prob !== null ? ($prob<=1 ? $prob*100 : $prob) : null;
|
||||
$sc = $probPct !== null ? ($probPct>=70?'#10b981':($probPct>=40?'#f59e0b':'#f43f5e')) : '#e5e7eb';
|
||||
$picked = isset($pickedNiks) ? $pickedNiks->contains($w->nik) : false;
|
||||
$rank = (($candidates->currentPage()-1)*$candidates->perPage())+$index+1;
|
||||
$rk = ['linear-gradient(135deg,#f59e0b,#d97706)','linear-gradient(135deg,#94a3b8,#64748b)','linear-gradient(135deg,#f97316,#ea580c)'];
|
||||
$rbg = $rank<=3 ? $rk[$rank-1] : '#f1f5f9';
|
||||
$rtc = $rank<=3 ? '#fff' : '#64748b';
|
||||
$svMap = ['pending'=>['#fffbeb','#b45309','#fde68a','Pending'],'disetujui'=>['#f0fdf4','#166534','#bbf7d0','Disetujui'],'ditolak'=>['#fff1f2','#9f1239','#fecdd3','Ditolak']];
|
||||
$sv = $svMap[$w->status_verifikasi??''] ?? ['#f3f4f6','#6b7280','#e5e7eb',ucfirst($w->status_verifikasi??'-')];
|
||||
@endphp
|
||||
<tr class="{{ $picked ? 'row-locked' : '' }}" style="{{ $picked ? 'background:#f0fdf4;' : '' }}">
|
||||
<td>
|
||||
<div style="display:flex;align-items:center;gap:5px;">
|
||||
<div class="rank-badge" style="background:{{ $rbg }};color:{{ $rtc }};">{{ $rank }}</div>
|
||||
@if($picked)
|
||||
<svg width="11" height="11" fill="none" stroke="#059669" viewBox="0 0 24 24" title="Sudah terpilih"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2.5" d="M12 15v2m-6 4h12a2 2 0 002-2v-6a2 2 0 00-2-2H6a2 2 0 00-2 2v6a2 2 0 002 2zm10-10V7a4 4 0 00-8 0v4h8z"/></svg>
|
||||
@endif
|
||||
</div>
|
||||
</td>
|
||||
<td>
|
||||
<div style="display:flex;align-items:center;gap:8px;">
|
||||
<div style="width:28px;height:28px;border-radius:8px;background:{{ $picked ? 'linear-gradient(135deg,#059669,#047857)' : 'linear-gradient(135deg,#3b82f6,#2563eb)' }};display:flex;align-items:center;justify-content:center;font-size:11px;font-weight:800;color:#fff;flex-shrink:0;">
|
||||
{{ strtoupper(substr($w->nama_lengkap,0,1)) }}
|
||||
</div>
|
||||
<div>
|
||||
<span style="font-size:12px;font-weight:600;color:#0f172a;">{{ $w->nama_lengkap }}</span>
|
||||
@if($picked)
|
||||
<div style="font-size:9.5px;color:#059669;font-weight:600;">Sudah ditetapkan</div>
|
||||
@endif
|
||||
</div>
|
||||
</div>
|
||||
</td>
|
||||
<td>
|
||||
<span style="font-family:monospace;font-size:10.5px;color:#6b7280;background:#f3f4f6;padding:2px 7px;border-radius:6px;">{{ $w->nik }}</span>
|
||||
</td>
|
||||
<td style="font-size:12px;color:#374151;">
|
||||
{{ optional(optional($w->rt)->dusun)->nama_dusun ?? '-' }}
|
||||
</td>
|
||||
<td>
|
||||
@if($probPct !== null)
|
||||
<div class="prob-wrap">
|
||||
<div class="prob-bg">
|
||||
<div class="prob-bar" style="width:{{ min($probPct,100) }}%;background:{{ $sc }};"></div>
|
||||
</div>
|
||||
<span style="font-size:11.5px;font-weight:700;color:{{ $sc }};white-space:nowrap;">{{ number_format((float)$probPct,1) }}%</span>
|
||||
</div>
|
||||
@else
|
||||
<span style="color:#d1d5db;">—</span>
|
||||
@endif
|
||||
</td>
|
||||
<td>
|
||||
<span class="badge" style="background:{{ $sv[0] }};color:{{ $sv[1] }};border:1px solid {{ $sv[2] }};">
|
||||
<span class="dot" style="background:{{ $sv[1] }};"></span>{{ $sv[3] }}
|
||||
</span>
|
||||
</td>
|
||||
<td>
|
||||
@if($picked)
|
||||
<span class="badge" style="background:#f0fdf4;color:#166534;border:1px solid #bbf7d0;">
|
||||
<span class="dot" style="background:#10b981;"></span>Terpilih
|
||||
</span>
|
||||
@else
|
||||
<span class="badge" style="background:#f3f4f6;color:#6b7280;border:1px solid #e5e7eb;">
|
||||
<span class="dot" style="background:#9ca3af;"></span>Belum
|
||||
</span>
|
||||
@endif
|
||||
</td>
|
||||
</tr>
|
||||
@endforeach
|
||||
@endif
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
{{-- MOBILE CARD VIEW --}}
|
||||
<div class="mob-fi">
|
||||
@forelse(($candidates??[]) as $index => $w)
|
||||
@php
|
||||
$prob = $w->prob ?? optional($w->prediksiKelayakan)->probability;
|
||||
$probPct = $prob !== null ? ($prob<=1 ? $prob*100 : $prob) : null;
|
||||
$sc = $probPct!==null ? ($probPct>=70?'#10b981':($probPct>=40?'#f59e0b':'#f43f5e')) : '#e5e7eb';
|
||||
$picked = isset($pickedIds) ? $pickedIds->contains($w->id) : false;
|
||||
$rank = (($candidates->currentPage()-1)*$candidates->perPage())+$index+1;
|
||||
$sv=['pending'=>['#fffbeb','#b45309','Pending'],'disetujui'=>['#f0fdf4','#166534','Disetujui'],'ditolak'=>['#fff1f2','#9f1239','Ditolak']];
|
||||
$sv=$sv[$w->status_verifikasi??'']??['#f3f4f6','#6b7280',ucfirst($w->status_verifikasi??'-')];
|
||||
@endphp
|
||||
<div class="mob-fi-row" style="{{ $picked ? 'background:#f0fdf4;border-color:#bbf7d0;' : '' }}">
|
||||
<div style="display:flex;align-items:center;gap:10px;">
|
||||
{{-- rank --}}
|
||||
@php
|
||||
$rk = ['linear-gradient(135deg,#f59e0b,#d97706)','linear-gradient(135deg,#94a3b8,#64748b)','linear-gradient(135deg,#f97316,#ea580c)'];
|
||||
$rbg = $rank<=3 ? $rk[$rank-1] : '#f1f5f9';
|
||||
$rtc = $rank<=3 ? '#fff' : '#64748b';
|
||||
@endphp
|
||||
<div class="rank-badge" style="background:{{ $rbg }};color:{{ $rtc }};flex-shrink:0;">{{ $rank }}</div>
|
||||
|
||||
{{-- avatar + nama --}}
|
||||
<div style="flex:1;min-width:0;">
|
||||
<div style="font-size:13px;font-weight:700;color:#0f172a;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;">{{ $w->nama_lengkap }}</div>
|
||||
<div style="font-size:10.5px;color:#64748b;margin-top:1px;font-family:monospace;">{{ $w->nik }}</div>
|
||||
</div>
|
||||
|
||||
{{-- final status --}}
|
||||
@if($picked)
|
||||
<span class="badge" style="background:#f0fdf4;color:#166534;border:1px solid #bbf7d0;flex-shrink:0;">✓ Terpilih</span>
|
||||
@endif
|
||||
</div>
|
||||
|
||||
<div style="display:flex;align-items:center;gap:8px;margin-top:10px;flex-wrap:wrap;">
|
||||
{{-- dusun --}}
|
||||
<span style="font-size:11px;color:#64748b;background:#f1f5f9;padding:2px 8px;border-radius:6px;">
|
||||
{{ optional(optional($w->rt)->dusun)->nama_dusun ?? '-' }}
|
||||
</span>
|
||||
|
||||
{{-- status --}}
|
||||
<span class="badge" style="background:{{ $sv[0] }};color:{{ $sv[1] }};border:1px solid {{ $sv[0] }};">
|
||||
<span class="dot" style="background:{{ $sv[1] }};"></span>{{ $sv[2] }}
|
||||
</span>
|
||||
|
||||
{{-- probabilitas --}}
|
||||
@if($probPct !== null)
|
||||
<div style="display:flex;align-items:center;gap:6px;flex:1;min-width:100px;">
|
||||
<div style="flex:1;height:5px;background:#e2e8f0;border-radius:99px;overflow:hidden;">
|
||||
<div style="height:100%;border-radius:99px;background:{{ $sc }};width:{{ min($probPct,100) }}%;"></div>
|
||||
</div>
|
||||
<span style="font-size:11px;font-weight:700;color:{{ $sc }};white-space:nowrap;">{{ number_format((float)$probPct,1) }}%</span>
|
||||
</div>
|
||||
@endif
|
||||
</div>
|
||||
</div>
|
||||
@empty
|
||||
@if(!isset($dusunId) || (int)$dusunId === 0)
|
||||
<div style="padding:32px 16px;text-align:center;">
|
||||
<p style="font-size:13px;font-weight:600;color:#6b7280;">Pilih dusun untuk menampilkan kandidat</p>
|
||||
</div>
|
||||
@endforelse
|
||||
@elseif($candidates->isEmpty())
|
||||
<div style="padding:32px 16px;text-align:center;">
|
||||
<p style="font-size:13px;font-weight:600;color:#6b7280;">Tidak ada kandidat dengan status <strong>sedang_validasi</strong> atau <strong>selesai</strong> di dusun ini</p>
|
||||
</div>
|
||||
@else
|
||||
@foreach($candidates as $index => $w)
|
||||
@php
|
||||
$prob = $w->prob ?? optional($w->prediksiKelayakan)->probability;
|
||||
$probPct = $prob !== null ? ($prob<=1 ? $prob*100 : $prob) : null;
|
||||
$sc = $probPct!==null ? ($probPct>=70?'#10b981':($probPct>=40?'#f59e0b':'#f43f5e')) : '#e5e7eb';
|
||||
$picked = isset($pickedNiks) ? $pickedNiks->contains($w->nik) : false;
|
||||
$rank = (($candidates->currentPage()-1)*$candidates->perPage())+$index+1;
|
||||
$rk = ['linear-gradient(135deg,#f59e0b,#d97706)','linear-gradient(135deg,#94a3b8,#64748b)','linear-gradient(135deg,#f97316,#ea580c)'];
|
||||
$rbg = $rank<=3 ? $rk[$rank-1] : '#f1f5f9';
|
||||
$rtc = $rank<=3 ? '#fff' : '#64748b';
|
||||
$svMap = ['pending'=>['#fffbeb','#b45309','Pending'],'disetujui'=>['#f0fdf4','#166534','Disetujui'],'ditolak'=>['#fff1f2','#9f1239','Ditolak']];
|
||||
$sv = $svMap[$w->status_verifikasi??''] ?? ['#f3f4f6','#6b7280',ucfirst($w->status_verifikasi??'-')];
|
||||
@endphp
|
||||
<div class="mob-fi-row {{ $picked ? 'row-locked' : '' }}" style="{{ $picked ? 'background:#f0fdf4;border-color:#bbf7d0;opacity:.6;' : '' }}">
|
||||
<div style="display:flex;align-items:center;gap:10px;">
|
||||
<div class="rank-badge" style="background:{{ $rbg }};color:{{ $rtc }};flex-shrink:0;">{{ $rank }}</div>
|
||||
<div style="flex:1;min-width:0;">
|
||||
<div style="font-size:13px;font-weight:700;color:#0f172a;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;">{{ $w->nama_lengkap }}</div>
|
||||
<div style="font-size:10.5px;color:#64748b;margin-top:1px;font-family:monospace;">{{ $w->nik }}</div>
|
||||
@if($picked)
|
||||
<div style="font-size:9.5px;color:#059669;font-weight:600;">🔒 Sudah ditetapkan</div>
|
||||
@endif
|
||||
</div>
|
||||
@if($picked)
|
||||
<span class="badge" style="background:#f0fdf4;color:#166534;border:1px solid #bbf7d0;flex-shrink:0;">✓ Terpilih</span>
|
||||
@endif
|
||||
</div>
|
||||
<div style="display:flex;align-items:center;gap:8px;margin-top:10px;flex-wrap:wrap;">
|
||||
<span style="font-size:11px;color:#64748b;background:#f1f5f9;padding:2px 8px;border-radius:6px;">
|
||||
{{ optional(optional($w->rt)->dusun)->nama_dusun ?? '-' }}
|
||||
</span>
|
||||
<span class="badge" style="background:{{ $sv[0] }};color:{{ $sv[1] }};border:1px solid {{ $sv[0] }};">
|
||||
<span class="dot" style="background:{{ $sv[1] }};"></span>{{ $sv[2] }}
|
||||
</span>
|
||||
@if($probPct !== null)
|
||||
<div style="display:flex;align-items:center;gap:6px;flex:1;min-width:100px;">
|
||||
<div style="flex:1;height:5px;background:#e2e8f0;border-radius:99px;overflow:hidden;">
|
||||
<div style="height:100%;border-radius:99px;background:{{ $sc }};width:{{ min($probPct,100) }}%;"></div>
|
||||
</div>
|
||||
<span style="font-size:11px;font-weight:700;color:{{ $sc }};white-space:nowrap;">{{ number_format((float)$probPct,1) }}%</span>
|
||||
</div>
|
||||
@endif
|
||||
</div>
|
||||
</div>
|
||||
@endforeach
|
||||
@endif
|
||||
</div>
|
||||
|
||||
{{-- PAGINATION --}}
|
||||
|
|
@ -333,10 +557,263 @@
|
|||
<div class="note-box">
|
||||
<b>Catatan:</b> Kandidat yang tampil adalah data warga pada tahap
|
||||
<b>sedang_validasi</b> atau <b>selesai</b>.
|
||||
Warga yang sudah ditetapkan sebelumnya <b>tidak ikut seleksi ulang</b> (ditampilkan redup/terkunci).<br>
|
||||
Saat tombol <b>Tetapkan Teratas</b> ditekan, sistem otomatis:<br>
|
||||
• memilih warga dengan probabilitas tertinggi sesuai kuota<br>
|
||||
• menetapkan yang lolos menjadi <b>disetujui</b><br>
|
||||
• menetapkan yang tidak lolos menjadi <b>ditolak</b>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
// ── DATA DUSUN DARI SERVER ──────────────────────────────────────
|
||||
// Jumlah kandidat aktif per dusun (exclude yang sudah terpilih)
|
||||
// Dikirim dari blade via PHP ke JS sebagai object
|
||||
const dusunData = {
|
||||
@foreach($dusuns as $d)
|
||||
"{{ $d->id }}": {
|
||||
nama: "{{ $d->nama_dusun }}",
|
||||
// Hitung kandidat aktif: total kandidat dusun ini minus yang sudah picked
|
||||
// Kita pakai nilai dari server yang sudah di-query
|
||||
kandidat: @php
|
||||
// Hitung kandidat aktif per dusun (tidak sudah terpilih)
|
||||
$jumlahKandidatDusun = \App\Models\CalonPenerima::query()
|
||||
->whereIn('tracking_status', ['sedang_validasi', 'selesai'])
|
||||
->whereHas('rt', fn($q) => $q->where('dusun_id', $d->id))
|
||||
->whereNotIn('nik', \App\Models\PenerimaFinal::where('nama_dusun', $d->nama_dusun)->where('status_verifikasi','disetujui')->pluck('nik')->toArray())
|
||||
->count();
|
||||
echo $jumlahKandidatDusun;
|
||||
@endphp
|
||||
},
|
||||
@endforeach
|
||||
};
|
||||
|
||||
// ── FORMAT INPUT DANA DENGAN TITIK ──────────────────────────────
|
||||
function formatDana(el) {
|
||||
let raw = el.value.replace(/\D/g, '');
|
||||
if (raw.length > 0) {
|
||||
el.value = parseInt(raw, 10).toLocaleString('id-ID');
|
||||
} else {
|
||||
el.value = '';
|
||||
}
|
||||
hitungKuota();
|
||||
}
|
||||
|
||||
function getDanaValue() {
|
||||
const raw = document.getElementById('inputDana').value.replace(/\./g, '').replace(/,/g, '');
|
||||
return parseFloat(raw) || 0;
|
||||
}
|
||||
|
||||
function rupiah(n) {
|
||||
return 'Rp ' + Math.round(n).toLocaleString('id-ID');
|
||||
}
|
||||
|
||||
// Sisa bulan rencana penyaluran (12 - jumlah periode yang sudah tercatat)
|
||||
const sisaBulan = {{ max(1, 13 - $nextPeriodeNum) }};
|
||||
|
||||
// ── HITUNG PENERIMA BULAN INI & SISA DANA ────────────────────────
|
||||
function hitungKuota() {
|
||||
const dana = getDanaValue();
|
||||
const nilaiPerOrang = 300000;
|
||||
|
||||
// Sembunyikan semua stat kalau input belum lengkap
|
||||
if (dana <= 0) {
|
||||
document.getElementById('penerimaOtomatis').textContent = '—';
|
||||
document.getElementById('danaStatRow').style.display = 'none';
|
||||
document.getElementById('rekomendasiBox').style.display = 'none';
|
||||
document.getElementById('formSimpanWrap').style.display = 'none';
|
||||
return;
|
||||
}
|
||||
|
||||
// Jatah dana untuk periode/bulan ini = total dana dibagi sisa bulan rencana
|
||||
const jatahBulanIni = dana / sisaBulan;
|
||||
const penerima = Math.floor(jatahBulanIni / nilaiPerOrang);
|
||||
|
||||
if (penerima <= 0) {
|
||||
document.getElementById('penerimaOtomatis').textContent = '0 orang';
|
||||
document.getElementById('danaStatRow').style.display = 'none';
|
||||
document.getElementById('rekomendasiBox').style.display = 'none';
|
||||
document.getElementById('formSimpanWrap').style.display = 'none';
|
||||
return;
|
||||
}
|
||||
|
||||
document.getElementById('penerimaOtomatis').textContent = penerima + ' orang';
|
||||
|
||||
const terpakai = penerima * nilaiPerOrang;
|
||||
const sisa = dana - terpakai;
|
||||
const periodeBerikutnya = sisa > 0 ? Math.floor((sisa / Math.max(1, sisaBulan - 1)) / nilaiPerOrang) : 0;
|
||||
|
||||
// Update stat row
|
||||
document.getElementById('danaStatRow').style.display = 'grid';
|
||||
|
||||
document.getElementById('danaTermakai').textContent = rupiah(terpakai);
|
||||
document.getElementById('danaTermakaiSub').textContent = penerima + ' orang x Rp 300.000';
|
||||
|
||||
if (sisa >= 0) {
|
||||
document.getElementById('danaSisa').textContent = rupiah(sisa);
|
||||
document.getElementById('danaSisaSub').textContent = sisa > 0 ? 'Tersisa untuk ' + Math.max(1, sisaBulan - 1) + ' bulan berikutnya' : 'Dana habis tepat';
|
||||
document.getElementById('danaSisa').className = 'dana-stat-val ' + (sisa > 0 ? 'green' : 'amber');
|
||||
} else {
|
||||
document.getElementById('danaSisa').textContent = rupiah(sisa);
|
||||
document.getElementById('danaSisaSub').textContent = 'Dana tidak mencukupi!';
|
||||
document.getElementById('danaSisa').className = 'dana-stat-val red';
|
||||
}
|
||||
|
||||
if (periodeBerikutnya > 0) {
|
||||
document.getElementById('estimasiPeriode').textContent = rupiah(sisa);
|
||||
document.getElementById('estimasiPeriodeSub').textContent = 'kira-kira ' + periodeBerikutnya + ' orang/bulan untuk ' + Math.max(1, sisaBulan - 1) + ' bulan ke depan';
|
||||
} else {
|
||||
document.getElementById('estimasiPeriode').textContent = sisa > 0 ? rupiah(sisa) : '—';
|
||||
document.getElementById('estimasiPeriodeSub').textContent = sisa > 0 ? 'Sisa tidak cukup 1 orang/bulan' : 'Dana habis';
|
||||
}
|
||||
|
||||
// Tampilkan form simpan periode
|
||||
document.getElementById('formSimpanWrap').style.display = 'block';
|
||||
|
||||
// Hitung rekomendasi per dusun proporsional
|
||||
hitungProporsional(penerima);
|
||||
}
|
||||
|
||||
function syncHidden() {
|
||||
const dana = getDanaValue();
|
||||
const jatahBulanIni = dana / sisaBulan;
|
||||
const penerima = Math.floor(jatahBulanIni / 300000);
|
||||
if (dana <= 0 || penerima <= 0) {
|
||||
alert('Dana belum cukup untuk membiayai minimal 1 penerima bulan ini.');
|
||||
return false;
|
||||
}
|
||||
document.getElementById('hiddenDanaAwal').value = dana;
|
||||
document.getElementById('hiddenPenerima').value = penerima;
|
||||
return true;
|
||||
}
|
||||
|
||||
function hitungProporsional(totalKuota) {
|
||||
// Total kandidat aktif semua dusun
|
||||
let totalKandidat = 0;
|
||||
for (const id in dusunData) {
|
||||
totalKandidat += dusunData[id].kandidat;
|
||||
}
|
||||
|
||||
const listEl = document.getElementById('rekomendasiList');
|
||||
listEl.innerHTML = '';
|
||||
|
||||
if (totalKandidat === 0) {
|
||||
document.getElementById('rekomendasiBox').style.display = 'none';
|
||||
return;
|
||||
}
|
||||
|
||||
let sisaKuota = totalKuota;
|
||||
const entries = Object.entries(dusunData).filter(([,v]) => v.kandidat > 0);
|
||||
|
||||
// Hitung kuota proporsional dengan metode Hamilton (sisa terbesar)
|
||||
const kuotaFloat = entries.map(([id, v]) => ({
|
||||
id,
|
||||
nama: v.nama,
|
||||
kandidat: v.kandidat,
|
||||
kuotaFloat: (v.kandidat / totalKandidat) * totalKuota
|
||||
}));
|
||||
|
||||
// Assign floor dulu
|
||||
kuotaFloat.forEach(e => { e.kuota = Math.floor(e.kuotaFloat); });
|
||||
let sisaBagi = totalKuota - kuotaFloat.reduce((s, e) => s + e.kuota, 0);
|
||||
|
||||
// Distribusi sisa ke yang punya desimal terbesar
|
||||
kuotaFloat
|
||||
.sort((a, b) => (b.kuotaFloat - b.kuota) - (a.kuotaFloat - a.kuota))
|
||||
.forEach(e => {
|
||||
if (sisaBagi > 0) { e.kuota++; sisaBagi--; }
|
||||
});
|
||||
|
||||
// Pastikan tidak melebihi jumlah kandidat
|
||||
kuotaFloat.forEach(e => {
|
||||
e.kuota = Math.min(e.kuota, e.kandidat);
|
||||
});
|
||||
|
||||
// Sort by nama dusun
|
||||
kuotaFloat.sort((a, b) => a.nama.localeCompare(b.nama));
|
||||
|
||||
kuotaFloat.forEach(e => {
|
||||
const chip = document.createElement('div');
|
||||
chip.className = 'kuota-rec';
|
||||
chip.style.cursor = 'pointer';
|
||||
chip.title = 'Klik untuk pilih dusun ini & isi kuota ' + e.kuota;
|
||||
chip.innerHTML = `
|
||||
<svg width="11" height="11" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M17 20h5v-2a3 3 0 00-5.356-1.857M17 20H7m10 0v-2c0-.656-.126-1.283-.356-1.857M7 20H2v-2a3 3 0 015.356-1.857M7 20v-2c0-.656.126-1.283.356-1.857m0 0a5.002 5.002 0 019.288 0M15 7a3 3 0 11-6 0 3 3 0 016 0z"/></svg>
|
||||
<b>${e.nama}</b>: <span style="color:#059669;">${e.kuota} orang</span>
|
||||
<span style="color:#94a3b8;font-size:9.5px;">(${e.kandidat} kandidat aktif)</span>
|
||||
`;
|
||||
chip.addEventListener('click', () => {
|
||||
// Set dusun di select
|
||||
const sel = document.getElementById('selectDusun');
|
||||
for (let i = 0; i < sel.options.length; i++) {
|
||||
if (sel.options[i].getAttribute('data-nama') === e.nama) {
|
||||
sel.selectedIndex = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
// Set kuota
|
||||
document.getElementById('inputKuota').value = e.kuota;
|
||||
});
|
||||
listEl.appendChild(chip);
|
||||
});
|
||||
|
||||
document.getElementById('rekomendasiBox').style.display = 'block';
|
||||
}
|
||||
|
||||
// ── TOMBOL TAMPILKAN ────────────────────────────────────────────
|
||||
document.getElementById('formTampil')?.addEventListener('submit', function () {
|
||||
const btn = document.getElementById('btnTampil');
|
||||
const spinner = document.getElementById('spinnerTampil');
|
||||
const icon = document.getElementById('iconTampil');
|
||||
const text = document.getElementById('textTampil');
|
||||
|
||||
btn.disabled = true;
|
||||
btn.style.opacity = '0.8';
|
||||
btn.style.cursor = 'not-allowed';
|
||||
spinner.style.display = 'block';
|
||||
icon.style.display = 'none';
|
||||
text.textContent = 'Memuat...';
|
||||
|
||||
// Tampilkan overlay
|
||||
const overlay = document.getElementById('fi-overlay');
|
||||
document.getElementById('fi-overlay-ring').className = 'fi-overlay-ring blue';
|
||||
document.getElementById('fi-overlay-title').textContent = 'Memuat Kandidat';
|
||||
document.getElementById('fi-overlay-sub').textContent = 'Mengambil data dari server...';
|
||||
overlay.classList.add('show');
|
||||
});
|
||||
|
||||
// ── TOMBOL TETAPKAN ─────────────────────────────────────────────
|
||||
document.getElementById('formTetapkan')?.addEventListener('submit', function (e) {
|
||||
const kuota = {{ (int)($kuota??7) }};
|
||||
if (!confirm('Tetapkan ' + kuota + ' orang dengan probabilitas tertinggi untuk dusun ini?\n\nWarga yang sudah terpilih sebelumnya tidak akan terpengaruh.')) {
|
||||
e.preventDefault();
|
||||
return;
|
||||
}
|
||||
|
||||
const btn = document.getElementById('btnTetapkan');
|
||||
const spinner = document.getElementById('spinnerTetapkan');
|
||||
const icon = document.getElementById('iconTetapkan');
|
||||
const text = document.getElementById('textTetapkan');
|
||||
|
||||
btn.disabled = true;
|
||||
btn.style.opacity = '0.8';
|
||||
btn.style.cursor = 'not-allowed';
|
||||
spinner.style.display = 'block';
|
||||
icon.style.display = 'none';
|
||||
text.textContent = 'Memproses...';
|
||||
|
||||
// Tampilkan overlay
|
||||
const overlay = document.getElementById('fi-overlay');
|
||||
document.getElementById('fi-overlay-ring').className = 'fi-overlay-ring';
|
||||
document.getElementById('fi-overlay-title').textContent = 'Menetapkan Penerima';
|
||||
document.getElementById('fi-overlay-sub').textContent = 'Sistem sedang memproses & menetapkan kandidat...';
|
||||
overlay.classList.add('show');
|
||||
});
|
||||
|
||||
// Init jika ada nilai dana dari query string
|
||||
(function(){
|
||||
const dana = document.getElementById('inputDana').value;
|
||||
if (dana) hitungKuota();
|
||||
})();
|
||||
</script>
|
||||
|
||||
</x-app-layout>
|
||||
|
|
@ -4,7 +4,7 @@
|
|||
<div style="display:flex;align-items:center;justify-content:space-between;gap:12px;flex-wrap:wrap;">
|
||||
<div>
|
||||
<h2 style="font-size:16px;font-weight:800;color:#0f172a;letter-spacing:-.02em;">Laporan Penerima BLT-DD</h2>
|
||||
<p style="font-size:11px;color:#94a3b8;margin-top:2px;">Kelurahan Ngerong — warga yang telah ditetapkan sebagai penerima final</p>
|
||||
<p style="font-size:11px;color:#94a3b8;margin-top:2px;">DesaNgerong — warga yang telah ditetapkan sebagai penerima final</p>
|
||||
</div>
|
||||
</div>
|
||||
</x-slot>
|
||||
|
|
@ -42,6 +42,7 @@
|
|||
.btn-pdf{background:linear-gradient(135deg,#ef4444,#dc2626);color:#fff;box-shadow:0 3px 12px rgba(220,38,38,.3);}
|
||||
.btn-excel{background:linear-gradient(135deg,#22c55e,#16a34a);color:#fff;box-shadow:0 3px 12px rgba(22,163,74,.3);}
|
||||
.btn-send{background:linear-gradient(135deg,#3b82f6,#2563eb);color:#fff;box-shadow:0 3px 12px rgba(37,99,235,.3);}
|
||||
.btn-reset{background:linear-gradient(135deg,#f59e0b,#d97706);color:#fff;box-shadow:0 3px 12px rgba(217,119,6,.3);}
|
||||
.btn-gray{display:inline-flex;align-items:center;padding:8px 14px;border:none;border-radius:10px;background:#e5e7eb;color:#374151;font-size:12px;font-weight:700;cursor:pointer;font-family:inherit;}
|
||||
|
||||
/* upload form */
|
||||
|
|
@ -50,7 +51,7 @@
|
|||
@media(max-width:768px){.pub-form{grid-template-columns:1fr 1fr;}}
|
||||
@media(max-width:500px){.pub-form{grid-template-columns:1fr;}}
|
||||
@media(max-width:500px){.pub-form .exp-btn{justify-content:center;}}
|
||||
.pub-input{padding:10px 13px;border:1.5px solid #e2e8f0;border-radius:11px;font-size:16px;/* prevent iOS zoom */outline:none;background:#fff;color:#0f172a;width:100%;font-family:inherit;}
|
||||
.pub-input{padding:10px 13px;border:1.5px solid #e2e8f0;border-radius:11px;font-size:16px;outline:none;background:#fff;color:#0f172a;width:100%;font-family:inherit;}
|
||||
.pub-input:focus{border-color:#93c5fd;box-shadow:0 0 0 3px rgba(37,99,235,.08);}
|
||||
.pub-list{margin-top:14px;display:flex;flex-direction:column;gap:10px;}
|
||||
.pub-item{display:flex;justify-content:space-between;align-items:center;gap:12px;padding:12px 14px;border:1.5px solid #f1f5f9;border-radius:13px;background:#fafbfc;flex-wrap:wrap;}
|
||||
|
|
@ -101,10 +102,28 @@
|
|||
.bantuan-tdk{display:inline-flex;padding:2px 8px;border-radius:20px;background:#f8fafc;color:#94a3b8;font-size:10px;font-weight:600;}
|
||||
.flash-ok{display:flex;align-items:center;gap:8px;background:#f0fdf4;border:1.5px solid #bbf7d0;color:#166534;padding:11px 16px;border-radius:13px;font-size:12.5px;font-weight:600;margin-bottom:14px;}
|
||||
.flash-ok svg{width:15px;height:15px;flex-shrink:0;}
|
||||
.flash-err{display:flex;align-items:center;gap:8px;background:#fff1f2;border:1.5px solid #fecdd3;color:#9f1239;padding:11px 16px;border-radius:13px;font-size:12.5px;font-weight:600;margin-bottom:14px;}
|
||||
.flash-err svg{width:15px;height:15px;flex-shrink:0;}
|
||||
.empty-state{padding:52px 16px;text-align:center;}
|
||||
.empty-icon{width:46px;height:46px;background:#f1f5f9;border-radius:50%;display:flex;align-items:center;justify-content:center;margin:0 auto 10px;}
|
||||
.tbl-foot{padding:9px 16px;border-top:1.5px solid #f1f5f9;background:#fafbfc;text-align:center;}
|
||||
.credit{font-size:11px;color:#cbd5e1;font-weight:500;}
|
||||
|
||||
/* riwayat accordion */
|
||||
.rw-periode{border:1.5px solid #f1f5f9;border-radius:13px;overflow:hidden;margin-bottom:10px;}
|
||||
.rw-periode-hd{display:flex;align-items:center;justify-content:space-between;padding:11px 14px;background:#fafbfc;cursor:pointer;user-select:none;gap:8px;}
|
||||
.rw-periode-hd:hover{background:#f1f5f9;}
|
||||
.rw-periode-title{font-size:12.5px;font-weight:800;color:#1e293b;}
|
||||
.rw-periode-meta{font-size:11px;color:#94a3b8;}
|
||||
.rw-periode-body{display:none;overflow-x:auto;}
|
||||
.rw-periode-body.open{display:block;}
|
||||
table.rw-tbl{width:100%;border-collapse:collapse;min-width:700px;}
|
||||
table.rw-tbl thead tr{background:#f8fafc;}
|
||||
table.rw-tbl thead th{padding:8px 10px;font-size:9px;font-weight:800;color:#94a3b8;text-transform:uppercase;letter-spacing:.07em;text-align:left;white-space:nowrap;}
|
||||
table.rw-tbl tbody tr{border-top:1px solid #f8fafc;}
|
||||
table.rw-tbl tbody td{padding:8px 10px;font-size:11.5px;color:#374151;vertical-align:middle;}
|
||||
.chev{width:14px;height:14px;transition:transform .2s;flex-shrink:0;color:#94a3b8;}
|
||||
.chev.open{transform:rotate(180deg);}
|
||||
</style>
|
||||
|
||||
{{-- FLASH --}}
|
||||
|
|
@ -114,6 +133,12 @@
|
|||
{{ session('success') }}
|
||||
</div>
|
||||
@endif
|
||||
@if(session('error'))
|
||||
<div class="flash-err">
|
||||
<svg fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 9v2m0 4h.01M12 3a9 9 0 100 18A9 9 0 0012 3z"/></svg>
|
||||
{{ session('error') }}
|
||||
</div>
|
||||
@endif
|
||||
|
||||
@php
|
||||
$totalPenerima = $laporans->count();
|
||||
|
|
@ -182,6 +207,20 @@
|
|||
Kirim ke RT
|
||||
</button>
|
||||
</form>
|
||||
|
||||
{{-- ── TOMBOL RESET MANUAL (BARU) ── --}}
|
||||
@if($laporans->count() > 0)
|
||||
<form action="{{ route('admin.laporan.reset-manual') }}" method="POST" style="display:contents;">
|
||||
@csrf
|
||||
<button type="submit" class="exp-btn btn-reset"
|
||||
onclick="return confirm('Arsipkan semua {{ $laporans->count() }} data penerima final ke riwayat lalu kosongkan tabel? Tindakan ini tidak dapat dibatalkan.')">
|
||||
<svg fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 4v5h.582m15.356 2A8.001 8.001 0 004.582 9m0 0H9m11 11v-5h-.581m0 0a8.003 8.003 0 01-15.357-2m15.357 2H15"/></svg>
|
||||
Reset & Arsipkan
|
||||
</button>
|
||||
</form>
|
||||
@endif
|
||||
{{-- ────────────────────────────── --}}
|
||||
|
||||
<span style="font-size:11px;color:#94a3b8;width:100%;padding-top:2px;">{{ $totalPenerima }} penerima terdaftar</span>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -341,14 +380,11 @@
|
|||
$scBd = $prob>=70?'#bbf7d0':($prob>=40?'#fde68a':'#fecdd3');
|
||||
@endphp
|
||||
<div class="mob-card">
|
||||
{{-- Header --}}
|
||||
<div class="mob-top">
|
||||
<div class="mob-nomor">{{ $index+1 }}</div>
|
||||
<div class="av">{{ $init }}</div>
|
||||
<div class="mob-name">{{ $nama }}</div>
|
||||
</div>
|
||||
|
||||
{{-- NIK & KK --}}
|
||||
<div style="display:flex;gap:10px;flex-wrap:wrap;">
|
||||
<div class="mob-kv">
|
||||
<span class="mob-k">NIK</span>
|
||||
|
|
@ -359,8 +395,6 @@
|
|||
<span class="mob-nik">{{ $item->no_kk??'-' }}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{-- Grid info --}}
|
||||
<div class="mob-rows">
|
||||
<div class="mob-kv">
|
||||
<span class="mob-k">RT</span>
|
||||
|
|
@ -399,8 +433,6 @@
|
|||
@endif
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{-- Bottom: prob + status + jumlah bantuan --}}
|
||||
<div class="mob-bottom">
|
||||
<div class="prob-cell">
|
||||
<div class="prob-bg"><div class="prob-fill" style="width:{{ min($prob,100) }}%;background:{{ $sc }};"></div></div>
|
||||
|
|
@ -413,8 +445,6 @@
|
|||
Rp {{ number_format($item->jumlah_bantuan??300000,0,',','.') }}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
{{-- Periode & penetapan --}}
|
||||
<div style="font-size:10.5px;color:#94a3b8;">
|
||||
Periode: <strong style="color:#475569;">{{ $item->periode_bantuan??'-' }}</strong>
|
||||
• Penetapan: <strong style="color:#475569;">{{ $item->tanggal_penetapan?->format('d-m-Y')??'-' }}</strong>
|
||||
|
|
@ -428,7 +458,102 @@
|
|||
@endforelse
|
||||
</div>
|
||||
|
||||
<div class="tbl-foot"><span class="credit">SiBantuDes · Kelurahan Ngerong</span></div>
|
||||
<div class="tbl-foot"><span class="credit">SiBantuDes · Desa Ngerong</span></div>
|
||||
</div>
|
||||
|
||||
{{-- ── RIWAYAT PENERIMA (BARU) ─────────────────────────────────────── --}}
|
||||
<div class="lp-card">
|
||||
<div class="sec-hd">
|
||||
<div class="sec-hd-l">
|
||||
<div class="sec-bar" style="background:linear-gradient(180deg,#f59e0b,#d97706);"></div>
|
||||
<span class="sec-title">Riwayat Penerima per Periode</span>
|
||||
</div>
|
||||
<span class="sec-count">{{ isset($riwayats) ? $riwayats->count() : 0 }} periode</span>
|
||||
</div>
|
||||
|
||||
<div style="padding:14px 16px;">
|
||||
@if(isset($riwayats) && $riwayats->count() > 0)
|
||||
@foreach($riwayats as $periode => $rows)
|
||||
<div class="rw-periode">
|
||||
<div class="rw-periode-hd" onclick="toggleRiwayat('rw-{{ Str::slug($periode) }}')">
|
||||
<div>
|
||||
<div class="rw-periode-title">Periode {{ $periode }}</div>
|
||||
<div class="rw-periode-meta">
|
||||
{{ $rows->count() }} penerima •
|
||||
Diarsipkan: {{ $rows->first()->di_arsipkan_pada?->format('d-m-Y H:i') ?? '-' }}
|
||||
</div>
|
||||
</div>
|
||||
<svg class="chev" id="chev-rw-{{ Str::slug($periode) }}" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7"/>
|
||||
</svg>
|
||||
</div>
|
||||
<div class="rw-periode-body" id="rw-{{ Str::slug($periode) }}">
|
||||
<table class="rw-tbl">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>#</th>
|
||||
<th>Nama</th>
|
||||
<th>NIK</th>
|
||||
<th>Dusun</th>
|
||||
<th>Pekerjaan</th>
|
||||
<th>Penghasilan</th>
|
||||
<th>Prob</th>
|
||||
<th>Status</th>
|
||||
<th>Bantuan</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@foreach($rows as $ri => $r)
|
||||
@php
|
||||
$rp = $r->probability ?? 0;
|
||||
if($rp<=1) $rp=$rp*100;
|
||||
$rc = $rp>=70?'#10b981':($rp>=40?'#f59e0b':'#f43f5e');
|
||||
@endphp
|
||||
<tr>
|
||||
<td style="color:#94a3b8;font-size:10.5px;font-weight:700;">{{ $ri+1 }}</td>
|
||||
<td style="font-weight:600;color:#0f172a;">{{ $r->nama_lengkap }}</td>
|
||||
<td><span class="nik-pill">{{ $r->nik }}</span></td>
|
||||
<td>{{ $r->nama_dusun ?? '-' }}</td>
|
||||
<td>{{ $r->pekerjaan ?? '-' }}</td>
|
||||
<td style="white-space:nowrap;">Rp {{ number_format($r->penghasilan??0,0,',','.') }}</td>
|
||||
<td>
|
||||
<span style="font-size:11px;font-weight:800;color:{{ $rc }};">{{ number_format($rp,1) }}%</span>
|
||||
</td>
|
||||
<td>
|
||||
<span class="st-pill" style="background:{{ $rp>=70?'#f0fdf4':($rp>=40?'#fffbeb':'#fff1f2') }};color:{{ $rc }};border:1px solid {{ $rp>=70?'#bbf7d0':($rp>=40?'#fde68a':'#fecdd3') }};">
|
||||
<span class="st-dot" style="background:{{ $rc }};"></span>
|
||||
{{ ucfirst($r->status_verifikasi ?? '-') }}
|
||||
</span>
|
||||
</td>
|
||||
<td style="font-weight:700;color:#166534;white-space:nowrap;">Rp {{ number_format($r->jumlah_bantuan??0,0,',','.') }}</td>
|
||||
</tr>
|
||||
@endforeach
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
@endforeach
|
||||
@else
|
||||
<div class="empty-state">
|
||||
<div class="empty-icon">
|
||||
<svg fill="none" stroke="#d1d5db" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" d="M12 8v4l3 3m6-3a9 9 0 11-18 0 9 9 0 0118 0z"/></svg>
|
||||
</div>
|
||||
<p style="font-size:13px;font-weight:600;color:#94a3b8;">Belum ada riwayat arsip penerima</p>
|
||||
<p style="font-size:11px;color:#cbd5e1;margin-top:4px;">Riwayat akan muncul setelah klik "Reset & Arsipkan"</p>
|
||||
</div>
|
||||
@endif
|
||||
</div>
|
||||
</div>
|
||||
{{-- ────────────────────────────────────────────────────────────────── --}}
|
||||
|
||||
<script>
|
||||
function toggleRiwayat(id) {
|
||||
const body = document.getElementById(id);
|
||||
const chev = document.getElementById('chev-' + id);
|
||||
if (!body) return;
|
||||
body.classList.toggle('open');
|
||||
chev?.classList.toggle('open');
|
||||
}
|
||||
</script>
|
||||
|
||||
</x-app-layout>
|
||||
File diff suppressed because it is too large
Load Diff
|
|
@ -437,7 +437,7 @@
|
|||
d="M17.657 16.657L13.414 20.9a1.998 1.998 0 01-2.827 0l-4.244-4.243a8 8 0 1111.314 0z"/>
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 11a3 3 0 11-6 0 3 3 0 016 0z"/>
|
||||
</svg>
|
||||
Kelurahan Ngerong
|
||||
Desa Ngerong
|
||||
</div>
|
||||
|
||||
<div class="left-caption">
|
||||
|
|
@ -486,7 +486,7 @@
|
|||
</a>
|
||||
|
||||
<div class="form-head">
|
||||
<div class="form-eyebrow">BLT-DD • Kelurahan Ngerong</div>
|
||||
<div class="form-eyebrow">BLT-DD • Desa Ngerong</div>
|
||||
<div class="form-title">Selamat Datang</div>
|
||||
<div class="form-sub">Masuk dengan akun yang telah terdaftar</div>
|
||||
</div>
|
||||
|
|
@ -543,7 +543,7 @@
|
|||
<label for="remember">Ingat saya di perangkat ini</label>
|
||||
</div>
|
||||
|
||||
<button type="submit" class="btn-submit">Masuk ke Sistem</button>
|
||||
<button type="submit" class="btn-submit" id="btn-login" onclick="handleLogin(this)">Masuk ke Sistem</button>
|
||||
</form>
|
||||
|
||||
<div class="reg-row">
|
||||
|
|
@ -573,7 +573,50 @@ function togglePw() {
|
|||
ico.innerHTML = `<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 12a3 3 0 11-6 0 3 3 0 016 0z"/><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M2.458 12C3.732 7.943 7.523 5 12 5c4.478 0 8.268 2.943 9.542 7-1.274 4.057-5.064 7-9.542 7-4.477 0-8.268-2.943-9.542-7z"/>`;
|
||||
}
|
||||
}
|
||||
|
||||
function handleLogin(btn) {
|
||||
/* validasi sederhana sebelum submit */
|
||||
var email = document.getElementById('email').value.trim();
|
||||
var pass = document.getElementById('password').value;
|
||||
if (!email || !pass) return; /* biarkan browser handle required */
|
||||
|
||||
/* ubah tombol jadi loading state */
|
||||
btn.disabled = true;
|
||||
btn.innerHTML =
|
||||
'<svg width="16" height="16" fill="none" stroke="currentColor" viewBox="0 0 24 24" ' +
|
||||
'style="animation:spin .8s linear infinite;flex-shrink:0;">' +
|
||||
'<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" ' +
|
||||
'd="M4 4v5h.582m15.356 2A8.001 8.001 0 004.582 9m0 0H9m11 11v-5h-.581' +
|
||||
'm0 0a8.003 8.003 0 01-15.357-2m15.357 2H15"/></svg>' +
|
||||
' Sedang masuk...';
|
||||
btn.style.display = 'inline-flex';
|
||||
btn.style.alignItems = 'center';
|
||||
btn.style.justifyContent = 'center';
|
||||
btn.style.gap = '8px';
|
||||
|
||||
/* submit form */
|
||||
btn.closest('form').submit();
|
||||
}
|
||||
|
||||
/* re-enable tombol jika user balik (bfcache / back button) */
|
||||
window.addEventListener('pageshow', function(e) {
|
||||
if (e.persisted) {
|
||||
var btn = document.getElementById('btn-login');
|
||||
if (btn) {
|
||||
btn.disabled = false;
|
||||
btn.innerHTML = 'Masuk ke Sistem';
|
||||
btn.style.display = '';
|
||||
btn.style.alignItems = '';
|
||||
btn.style.justifyContent = '';
|
||||
btn.style.gap = '';
|
||||
}
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<style>
|
||||
@keyframes spin { to { transform: rotate(360deg); } }
|
||||
</style>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
|
|
@ -244,7 +244,7 @@
|
|||
d="M17.657 16.657L13.414 20.9a1.998 1.998 0 01-2.827 0l-4.244-4.243a8 8 0 1111.314 0z"/>
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 11a3 3 0 11-6 0 3 3 0 016 0z"/>
|
||||
</svg>
|
||||
Kelurahan Ngerong
|
||||
Desa Ngerong
|
||||
</div>
|
||||
<div class="left-caption">
|
||||
Daftar & Kelola<br>Data Warga <span>BLT-DD</span><br>Secara Digital
|
||||
|
|
@ -264,7 +264,7 @@
|
|||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2.5" d="M5 13l4 4L19 7"/>
|
||||
</svg>
|
||||
</div>
|
||||
<span>Validasi & pengajuan ke kelurahan</span>
|
||||
<span>Validasi & pengajuan ke Admin Prangkat Desa</span>
|
||||
</div>
|
||||
<div class="feat">
|
||||
<div class="feat-dot">
|
||||
|
|
@ -282,7 +282,7 @@
|
|||
<!-- ── RIGHT PANEL ── -->
|
||||
<div class="right">
|
||||
|
||||
<div class="form-eyebrow">SiBantuDes • Kelurahan Ngerong</div>
|
||||
<div class="form-eyebrow">SiBantuDes • Desa Ngerong</div>
|
||||
<div class="form-title">Daftar Akun RT</div>
|
||||
<div class="form-sub">Lengkapi data untuk mendaftar ke sistem</div>
|
||||
|
||||
|
|
|
|||
File diff suppressed because one or more lines are too long
|
|
@ -28,7 +28,7 @@
|
|||
border:1.5px solid #e2e8f0;
|
||||
border-radius:16px;
|
||||
box-shadow:0 4px 24px rgba(15,23,42,.07);
|
||||
padding:10px 18px;
|
||||
padding:14px 22px;
|
||||
transition:border-radius .25s,box-shadow .25s;
|
||||
will-change:transform;
|
||||
transform:translateZ(0);
|
||||
|
|
@ -39,18 +39,19 @@
|
|||
box-shadow:0 8px 32px rgba(15,23,42,.10);
|
||||
}
|
||||
|
||||
.nb-row{ display:flex;align-items:center;gap:10px; }
|
||||
.nb-row{ display:flex;align-items:center;gap:12px; }
|
||||
|
||||
/* ── LINKS ── */
|
||||
.nb-links{ display:flex;align-items:center;gap:5px;flex:1; }
|
||||
.nb-links{ display:flex;align-items:center;gap:8px;flex:1; }
|
||||
|
||||
.nb-links a{
|
||||
display:inline-flex;align-items:center;
|
||||
padding:7px 13px;border-radius:10px;
|
||||
font-size:12.5px;font-weight:600;
|
||||
color:#64748b;background:#f8fafc;
|
||||
padding:10px 18px;border-radius:12px;
|
||||
font-size:15px;font-weight:700;
|
||||
color:#475569;background:#f8fafc;
|
||||
border:1.5px solid transparent;
|
||||
text-decoration:none;white-space:nowrap;
|
||||
letter-spacing:.01em;
|
||||
transition:background .15s,color .15s,border-color .15s,box-shadow .15s,
|
||||
max-width .2s ease, opacity .2s ease, padding .2s ease, margin .2s ease;
|
||||
max-width:200px;
|
||||
|
|
@ -73,44 +74,100 @@
|
|||
}
|
||||
|
||||
/* ── USER ── */
|
||||
.nb-user{ display:flex;align-items:center;gap:8px;flex-shrink:0; }
|
||||
.nb-user{ display:flex;align-items:center;gap:10px;flex-shrink:0; }
|
||||
.nb-avatar{
|
||||
width:32px;height:32px;border-radius:50%;
|
||||
width:40px;height:40px;border-radius:50%;
|
||||
background:linear-gradient(135deg,#3b82f6,#2563eb);
|
||||
display:flex;align-items:center;justify-content:center;
|
||||
color:#fff;font-size:12px;font-weight:800;
|
||||
color:#fff;font-size:15px;font-weight:800;
|
||||
box-shadow:0 2px 8px rgba(37,99,235,.22);flex-shrink:0;
|
||||
}
|
||||
.nb-name{ font-size:12.5px;font-weight:600;color:#374151;white-space:nowrap; }
|
||||
.nb-name{ font-size:14px;font-weight:700;color:#374151;white-space:nowrap; }
|
||||
.nb-logout{
|
||||
font-size:12px;font-weight:700;color:#ef4444;
|
||||
font-size:14px;font-weight:700;color:#ef4444;
|
||||
background:#fff1f2;border:1.5px solid #fecdd3;
|
||||
padding:5px 12px;border-radius:9px;cursor:pointer;
|
||||
padding:8px 16px;border-radius:10px;cursor:pointer;
|
||||
transition:background .15s;white-space:nowrap;
|
||||
}
|
||||
.nb-logout:hover{ background:#ffe4e6; }
|
||||
|
||||
/* ══ MOBILE ══ */
|
||||
@media(max-width:768px){
|
||||
#nb-wrap{ padding:8px 12px 0; }
|
||||
#nb-shell{ padding:9px 13px; }
|
||||
.nb-row{ flex-direction:column;align-items:stretch;gap:8px; }
|
||||
#nb-wrap{ padding:8px 8px 0; }
|
||||
#nb-shell{ padding:10px 12px 12px; border-radius:14px; }
|
||||
.nb-row{ flex-direction:column; align-items:stretch; gap:8px; }
|
||||
|
||||
/* Baris user: avatar + nama + tombol keluar */
|
||||
.nb-user{
|
||||
justify-content:space-between;
|
||||
width:100%;
|
||||
background:#f8fafc;
|
||||
border:1.5px solid #e2e8f0;
|
||||
border-radius:12px;
|
||||
padding:10px 14px;
|
||||
}
|
||||
.nb-user > div:first-child{ display:flex; align-items:center; gap:10px; flex:1; }
|
||||
.nb-avatar{ width:38px; height:38px; font-size:16px; flex-shrink:0; }
|
||||
.nb-name{
|
||||
display:block !important;
|
||||
font-size:15px;
|
||||
font-weight:700;
|
||||
color:#1e293b;
|
||||
}
|
||||
.nb-logout{
|
||||
font-size:14px;
|
||||
font-weight:700;
|
||||
padding:9px 18px;
|
||||
border-radius:10px;
|
||||
min-height:44px;
|
||||
display:flex;
|
||||
align-items:center;
|
||||
white-space:nowrap;
|
||||
flex-shrink:0;
|
||||
}
|
||||
|
||||
/* Nav links: scroll horizontal, semua label tampil penuh */
|
||||
.nb-links{
|
||||
overflow-x:auto;-webkit-overflow-scrolling:touch;
|
||||
scrollbar-width:none;gap:5px;padding-bottom:1px;
|
||||
overflow-x:auto;
|
||||
-webkit-overflow-scrolling:touch;
|
||||
scrollbar-width:none;
|
||||
gap:6px;
|
||||
padding-bottom:2px;
|
||||
display:flex !important;
|
||||
flex-wrap:nowrap;
|
||||
}
|
||||
.nb-links::-webkit-scrollbar{ display:none; }
|
||||
.nb-links.hide-inactive a:not(.nb-active){
|
||||
max-width:0;opacity:0;
|
||||
padding-left:0;padding-right:0;
|
||||
margin:0;border-width:0;pointer-events:none;
|
||||
.nb-links a{
|
||||
font-size:14px !important;
|
||||
font-weight:700 !important;
|
||||
padding:12px 20px !important;
|
||||
border-radius:10px !important;
|
||||
min-height:48px !important;
|
||||
display:inline-flex !important;
|
||||
align-items:center !important;
|
||||
white-space:nowrap !important;
|
||||
/* Paksa tampil semua — override hide-inactive */
|
||||
max-width:none !important;
|
||||
opacity:1 !important;
|
||||
border-width:1.5px !important;
|
||||
pointer-events:auto !important;
|
||||
overflow:visible !important;
|
||||
}
|
||||
/* Paksa semua link tetap terlihat di mobile */
|
||||
.nb-links.hide-inactive a:not(.nb-active){
|
||||
max-width:none !important;
|
||||
opacity:1 !important;
|
||||
padding:12px 20px !important;
|
||||
margin:0 !important;
|
||||
border-width:1.5px !important;
|
||||
pointer-events:auto !important;
|
||||
}
|
||||
.nb-user{ justify-content:space-between;width:100%; }
|
||||
.nb-name{ display:none; }
|
||||
}
|
||||
|
||||
@media(max-width:400px){
|
||||
.nb-links a{ font-size:11.5px;padding:6px 10px; }
|
||||
.nb-logout{ font-size:11.5px;padding:5px 10px; }
|
||||
.nb-links a{ font-size:13px !important; padding:11px 16px !important; min-height:44px !important; }
|
||||
.nb-logout{ font-size:13px; padding:9px 14px; }
|
||||
.nb-name{ font-size:14px; }
|
||||
}
|
||||
|
||||
/* ══ LAYOUT ══ */
|
||||
|
|
@ -123,6 +180,12 @@
|
|||
max-width:80rem;margin:14px auto 24px;padding:0 16px;
|
||||
display:flex;flex-direction:column;gap:14px;
|
||||
}
|
||||
|
||||
@media(max-width:768px){
|
||||
.page-header{ padding:0 10px;margin-top:12px; }
|
||||
.page-header-inner{ padding:14px 16px;border-radius:14px; }
|
||||
.page-main{ padding:0 10px;margin-top:12px;margin-bottom:24px;gap:12px; }
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
|
|
@ -134,6 +197,18 @@
|
|||
|
||||
@php $role = Auth::user()->role ?? null; @endphp
|
||||
|
||||
<!-- USER -->
|
||||
<div class="nb-user">
|
||||
<div style="display:flex;align-items:center;gap:10px;">
|
||||
<div class="nb-avatar">{{ strtoupper(substr(Auth::user()->name ?? 'U', 0, 1)) }}</div>
|
||||
<span class="nb-name">{{ Auth::user()->name ?? '' }}</span>
|
||||
</div>
|
||||
<form method="POST" action="{{ route('logout') }}">
|
||||
@csrf
|
||||
<button type="submit" class="nb-logout">Keluar</button>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<!-- LINKS -->
|
||||
<nav class="nb-links" id="nb-links">
|
||||
@if($role === 'admin')
|
||||
|
|
@ -151,7 +226,7 @@ class="{{ request()->routeIs('admin.laporan') ? 'nb-active' : '' }}">Laporan</a>
|
|||
<a href="{{ route('dashboard') }}"
|
||||
class="{{ request()->routeIs('dashboard') ? 'nb-active' : '' }}">Dashboard</a>
|
||||
<a href="{{ route('rt.calon-penerima.create') }}"
|
||||
class="{{ request()->routeIs('rt.calon-penerima.create') ? 'nb-active' : '' }}">Pendataan</a>
|
||||
class="{{ request()->routeIs('rt.calon-penerima.create') ? 'nb-active' : '' }}">Inputkan Warga</a>
|
||||
<a href="{{ route('rt.calon-penerima.index') }}"
|
||||
class="{{ request()->routeIs('rt.calon-penerima.index') ? 'nb-active' : '' }}">Riwayat</a>
|
||||
<a href="{{ route('rt.laporan.index') }}"
|
||||
|
|
@ -159,18 +234,6 @@ class="{{ request()->routeIs('rt.laporan.*') ? 'nb-active' : '' }}">Laporan</a>
|
|||
@endif
|
||||
</nav>
|
||||
|
||||
<!-- USER -->
|
||||
<div class="nb-user">
|
||||
<div style="display:flex;align-items:center;gap:8px;">
|
||||
<div class="nb-avatar">{{ strtoupper(substr(Auth::user()->name ?? 'U', 0, 1)) }}</div>
|
||||
<span class="nb-name">{{ Auth::user()->name ?? '' }}</span>
|
||||
</div>
|
||||
<form method="POST" action="{{ route('logout') }}">
|
||||
@csrf
|
||||
<button type="submit" class="nb-logout">Keluar</button>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -200,17 +263,6 @@ class="{{ request()->routeIs('rt.laporan.*') ? 'nb-active' : '' }}">Laporan</a>
|
|||
: wrap.classList.add('scrolled');
|
||||
}, {threshold:0}).observe(sentinel);
|
||||
|
||||
/*
|
||||
* ── hide-inactive: DESKTOP ONLY ──
|
||||
*
|
||||
* Di mobile (≤768px) fitur ini dimatikan total karena:
|
||||
* - Konten halaman pendek → scrollY sering jitter di bawah
|
||||
* - iOS rubber-band / Android overscroll bounce bikin scrollY
|
||||
* naik-turun ±2-5px meski jari tidak bergerak → class toggle
|
||||
* bolak-balik tiap frame → kedip/getar
|
||||
* - Di mobile sudah ada horizontal scroll untuk nav links,
|
||||
* jadi collapse tidak diperlukan
|
||||
*/
|
||||
var MOBILE_BP = 768;
|
||||
|
||||
var lastY = window.scrollY;
|
||||
|
|
|
|||
|
|
@ -9,17 +9,23 @@
|
|||
<style>
|
||||
.input-field { font-size: 16px !important; }
|
||||
@media (min-width: 640px) { .input-field { font-size: 14px !important; } }
|
||||
|
||||
@media (max-width: 480px) {
|
||||
.action-row { flex-direction: column-reverse !important; }
|
||||
.action-row a,
|
||||
.action-row button { width: 100%; justify-content: center; text-align: center; }
|
||||
.action-row a, .action-row button { width: 100%; justify-content: center; text-align: center; }
|
||||
}
|
||||
|
||||
@media (max-width: 640px) {
|
||||
.section-body { padding: 14px 14px !important; }
|
||||
.section-head { padding: 10px 14px !important; }
|
||||
}
|
||||
@keyframes spin { to { transform: rotate(360deg); } }
|
||||
|
||||
/* Checkbox aset */
|
||||
.aset-checkbox-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(160px, 1fr)); gap: 8px; }
|
||||
.aset-checkbox-item { display: flex; align-items: center; gap: 8px; padding: 8px 12px;
|
||||
border: 1px solid #e5e7eb; border-radius: 10px; cursor: pointer; transition: background .15s, border-color .15s; }
|
||||
.aset-checkbox-item:has(input:checked) { background: #eff6ff; border-color: #93c5fd; }
|
||||
.aset-checkbox-item input[type=checkbox] { accent-color: #2563eb; width: 15px; height: 15px; flex-shrink: 0; }
|
||||
.aset-checkbox-item span { font-size: 13px; color: #374151; }
|
||||
</style>
|
||||
|
||||
@if ($errors->any())
|
||||
|
|
@ -54,26 +60,14 @@
|
|||
<div class="w-1 h-4 bg-blue-600 rounded-full"></div>
|
||||
<h3 class="text-sm font-semibold text-gray-700">Data Identitas</h3>
|
||||
</div>
|
||||
|
||||
<div class="section-body p-5 grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-4">
|
||||
@php
|
||||
$myRtId = auth()->user()->rt_id ?? (auth()->user()->rt->id ?? null);
|
||||
@endphp
|
||||
@php $myRtId = auth()->user()->rt_id ?? (auth()->user()->rt->id ?? null); @endphp
|
||||
|
||||
<div class="field-group">
|
||||
<label class="block text-xs font-semibold text-gray-600 mb-1">
|
||||
RT <span class="text-red-500">*</span>
|
||||
</label>
|
||||
<select
|
||||
id="rt_id_display"
|
||||
class="input-field w-full border border-gray-200 rounded-xl px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent bg-gray-100"
|
||||
disabled
|
||||
>
|
||||
<label class="block text-xs font-semibold text-gray-600 mb-1">RT <span class="text-red-500">*</span></label>
|
||||
<select id="rt_id_display" class="input-field w-full border border-gray-200 rounded-xl px-3 py-2 text-sm bg-gray-100" disabled>
|
||||
@foreach(($rts ?? []) as $rt)
|
||||
<option
|
||||
value="{{ $rt->id }}"
|
||||
{{ (string)$rt->id === (string)$myRtId ? 'selected' : '' }}
|
||||
>
|
||||
<option value="{{ $rt->id }}" {{ (string)$rt->id === (string)$myRtId ? 'selected' : '' }}>
|
||||
RT {{ str_pad($rt->nomor_rt, 3, '0', STR_PAD_LEFT) }} - {{ $rt->dusun->nama_dusun ?? '' }}
|
||||
</option>
|
||||
@endforeach
|
||||
|
|
@ -83,9 +77,7 @@ class="input-field w-full border border-gray-200 rounded-xl px-3 py-2 text-sm fo
|
|||
</div>
|
||||
|
||||
<div class="field-group">
|
||||
<label class="block text-xs font-semibold text-gray-600 mb-1">
|
||||
No. KK <span class="text-red-500">*</span>
|
||||
</label>
|
||||
<label class="block text-xs font-semibold text-gray-600 mb-1">No. KK <span class="text-red-500">*</span></label>
|
||||
<input type="text" name="no_kk" id="no_kk" maxlength="16"
|
||||
class="input-field w-full border border-gray-200 rounded-xl px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent bg-gray-50"
|
||||
placeholder="16 digit No. KK" required inputmode="numeric">
|
||||
|
|
@ -93,9 +85,7 @@ class="input-field w-full border border-gray-200 rounded-xl px-3 py-2 text-sm fo
|
|||
</div>
|
||||
|
||||
<div class="field-group">
|
||||
<label class="block text-xs font-semibold text-gray-600 mb-1">
|
||||
NIK <span class="text-red-500">*</span>
|
||||
</label>
|
||||
<label class="block text-xs font-semibold text-gray-600 mb-1">NIK <span class="text-red-500">*</span></label>
|
||||
<input type="text" name="nik" id="nik" maxlength="16"
|
||||
class="input-field w-full border border-gray-200 rounded-xl px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent bg-gray-50"
|
||||
placeholder="16 digit NIK" required inputmode="numeric">
|
||||
|
|
@ -103,9 +93,7 @@ class="input-field w-full border border-gray-200 rounded-xl px-3 py-2 text-sm fo
|
|||
</div>
|
||||
|
||||
<div class="field-group">
|
||||
<label class="block text-xs font-semibold text-gray-600 mb-1">
|
||||
Nama Lengkap <span class="text-red-500">*</span>
|
||||
</label>
|
||||
<label class="block text-xs font-semibold text-gray-600 mb-1">Nama Lengkap <span class="text-red-500">*</span></label>
|
||||
<input type="text" name="nama_lengkap" id="nama_lengkap"
|
||||
class="input-field w-full border border-gray-200 rounded-xl px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent bg-gray-50"
|
||||
placeholder="Nama sesuai KTP" required>
|
||||
|
|
@ -113,20 +101,18 @@ class="input-field w-full border border-gray-200 rounded-xl px-3 py-2 text-sm fo
|
|||
</div>
|
||||
|
||||
<div class="field-group">
|
||||
<label class="block text-xs font-semibold text-gray-600 mb-1">
|
||||
Jenis Kelamin <span class="text-red-500">*</span>
|
||||
</label>
|
||||
<select name="jenis_kelamin"
|
||||
<label class="block text-xs font-semibold text-gray-600 mb-1">Jenis Kelamin <span class="text-red-500">*</span></label>
|
||||
<select name="jenis_kelamin" id="jenis_kelamin"
|
||||
class="input-field w-full border border-gray-200 rounded-xl px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent bg-gray-50">
|
||||
<option value="">-- Pilih --</option>
|
||||
<option value="Laki-laki">Laki-laki</option>
|
||||
<option value="Perempuan">Perempuan</option>
|
||||
</select>
|
||||
<p class="error-msg text-xs text-red-500 mt-1 hidden">Jenis kelamin wajib dipilih.</p>
|
||||
</div>
|
||||
|
||||
<div class="field-group">
|
||||
<label class="block text-xs font-semibold text-gray-600 mb-1">
|
||||
Tempat Lahir <span class="text-red-500">*</span>
|
||||
</label>
|
||||
<label class="block text-xs font-semibold text-gray-600 mb-1">Tempat Lahir <span class="text-red-500">*</span></label>
|
||||
<input type="text" name="tempat_lahir" id="tempat_lahir"
|
||||
class="input-field w-full border border-gray-200 rounded-xl px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent bg-gray-50"
|
||||
placeholder="Kota/Kabupaten" required>
|
||||
|
|
@ -134,9 +120,7 @@ class="input-field w-full border border-gray-200 rounded-xl px-3 py-2 text-sm fo
|
|||
</div>
|
||||
|
||||
<div class="field-group">
|
||||
<label class="block text-xs font-semibold text-gray-600 mb-1">
|
||||
Tanggal Lahir <span class="text-red-500">*</span>
|
||||
</label>
|
||||
<label class="block text-xs font-semibold text-gray-600 mb-1">Tanggal Lahir <span class="text-red-500">*</span></label>
|
||||
<input type="date" name="tanggal_lahir" id="tanggal_lahir"
|
||||
class="input-field w-full border border-gray-200 rounded-xl px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent bg-gray-50"
|
||||
required>
|
||||
|
|
@ -144,9 +128,7 @@ class="input-field w-full border border-gray-200 rounded-xl px-3 py-2 text-sm fo
|
|||
</div>
|
||||
|
||||
<div class="field-group">
|
||||
<label class="block text-xs font-semibold text-gray-600 mb-1">
|
||||
Usia <span class="text-red-500">*</span>
|
||||
</label>
|
||||
<label class="block text-xs font-semibold text-gray-600 mb-1">Usia <span class="text-red-500">*</span></label>
|
||||
<input type="number" name="usia" id="usia" min="0" max="150"
|
||||
class="input-field w-full border border-gray-200 rounded-xl px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent bg-gray-50"
|
||||
placeholder="Otomatis dari tanggal lahir" required inputmode="numeric">
|
||||
|
|
@ -154,9 +136,7 @@ class="input-field w-full border border-gray-200 rounded-xl px-3 py-2 text-sm fo
|
|||
</div>
|
||||
|
||||
<div class="field-group">
|
||||
<label class="block text-xs font-semibold text-gray-600 mb-1">
|
||||
Status Perkawinan <span class="text-red-500">*</span>
|
||||
</label>
|
||||
<label class="block text-xs font-semibold text-gray-600 mb-1">Status Perkawinan <span class="text-red-500">*</span></label>
|
||||
<select name="status_perkawinan" id="status_perkawinan"
|
||||
class="input-field w-full border border-gray-200 rounded-xl px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent bg-gray-50">
|
||||
<option value="">-- Pilih --</option>
|
||||
|
|
@ -176,42 +156,24 @@ class="input-field w-full border border-gray-200 rounded-xl px-3 py-2 text-sm fo
|
|||
<div class="w-1 h-4 bg-emerald-500 rounded-full"></div>
|
||||
<h3 class="text-sm font-semibold text-gray-700">Data Tempat Tinggal</h3>
|
||||
</div>
|
||||
|
||||
<div class="section-body p-5 grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-4">
|
||||
<div class="field-group lg:col-span-2">
|
||||
<label class="block text-xs font-semibold text-gray-600 mb-1">
|
||||
Alamat <span class="text-red-500">*</span>
|
||||
</label>
|
||||
<div class="section-body p-5 grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||
<div class="field-group md:col-span-2 lg:col-span-1">
|
||||
<label class="block text-xs font-semibold text-gray-600 mb-1">Alamat <span class="text-red-500">*</span></label>
|
||||
<input type="text" name="alamat" id="alamat"
|
||||
class="input-field w-full border border-gray-200 rounded-xl px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent bg-gray-50"
|
||||
placeholder="Jalan, nomor rumah, RT/RW" required>
|
||||
<p class="error-msg text-xs text-red-500 mt-1 hidden">Alamat wajib diisi.</p>
|
||||
</div>
|
||||
|
||||
@php
|
||||
$myDusunName = auth()->user()->rt->dusun->nama_dusun ?? '';
|
||||
@endphp
|
||||
|
||||
@php $myDusunName = auth()->user()->rt->dusun->nama_dusun ?? ''; @endphp
|
||||
<div class="field-group">
|
||||
<label class="block text-xs font-semibold text-gray-600 mb-1">
|
||||
Dusun <span class="text-red-500">*</span>
|
||||
</label>
|
||||
<label class="block text-xs font-semibold text-gray-600 mb-1">Dusun <span class="text-red-500">*</span></label>
|
||||
<input type="text" id="desa_display"
|
||||
class="input-field w-full border border-gray-200 rounded-xl px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent bg-gray-100"
|
||||
class="input-field w-full border border-gray-200 rounded-xl px-3 py-2 text-sm bg-gray-100"
|
||||
value="{{ $myDusunName }}" readonly>
|
||||
<input type="hidden" name="desa" id="desa" value="{{ $myDusunName }}">
|
||||
<p class="error-msg text-xs text-red-500 mt-1 hidden">Dusun wajib diisi.</p>
|
||||
</div>
|
||||
|
||||
<div class="field-group">
|
||||
<label class="block text-xs font-semibold text-gray-600 mb-1">
|
||||
Aset Kepemilikan <span class="text-red-500">*</span>
|
||||
</label>
|
||||
<input type="text" name="aset_kepemilikan" id="aset_kepemilikan"
|
||||
class="input-field w-full border border-gray-200 rounded-xl px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent bg-gray-50"
|
||||
placeholder="Misal: Rumah, Motor, dll" required>
|
||||
<p class="error-msg text-xs text-red-500 mt-1 hidden">Aset kepemilikan wajib diisi.</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
|
@ -221,32 +183,37 @@ class="input-field w-full border border-gray-200 rounded-xl px-3 py-2 text-sm fo
|
|||
<div class="w-1 h-4 bg-amber-500 rounded-full"></div>
|
||||
<h3 class="text-sm font-semibold text-gray-700">Data Ekonomi</h3>
|
||||
</div>
|
||||
|
||||
<div class="section-body p-5 grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-4">
|
||||
<div class="field-group">
|
||||
<label class="block text-xs font-semibold text-gray-600 mb-1">
|
||||
Pekerjaan <span class="text-red-500">*</span>
|
||||
</label>
|
||||
<input type="text" name="pekerjaan" id="pekerjaan"
|
||||
class="input-field w-full border border-gray-200 rounded-xl px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent bg-gray-50"
|
||||
placeholder="Jenis pekerjaan" required>
|
||||
<p class="error-msg text-xs text-red-500 mt-1 hidden">Pekerjaan wajib diisi.</p>
|
||||
<label class="block text-xs font-semibold text-gray-600 mb-1">Pekerjaan <span class="text-red-500">*</span></label>
|
||||
<select name="pekerjaan" id="pekerjaan"
|
||||
class="input-field w-full border border-gray-200 rounded-xl px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent bg-gray-50">
|
||||
<option value="">-- Pilih --</option>
|
||||
<option value="Tidak Bekerja">Tidak Bekerja</option>
|
||||
<option value="Buruh Harian Lepas">Buruh Harian Lepas</option>
|
||||
<option value="Petani Kecil">Petani Kecil</option>
|
||||
<option value="Mengurus Rumah Tangga">Mengurus Rumah Tangga</option>
|
||||
<option value="Pedagang">Pedagang</option>
|
||||
<option value="Wiraswasta">Wiraswasta</option>
|
||||
<option value="Karyawan Swasta">Karyawan Swasta</option>
|
||||
<option value="Guru Honorer">Guru Honorer</option>
|
||||
<option value="PNS">PNS</option>
|
||||
<option value="TNI/Polri">TNI/Polri</option>
|
||||
</select>
|
||||
<p class="error-msg text-xs text-red-500 mt-1 hidden">Pekerjaan wajib dipilih.</p>
|
||||
</div>
|
||||
|
||||
<div class="field-group">
|
||||
<label class="block text-xs font-semibold text-gray-600 mb-1">
|
||||
Penghasilan (Rp) <span class="text-red-500">*</span>
|
||||
</label>
|
||||
<input type="number" step="0.01" name="penghasilan" id="penghasilan" min="0"
|
||||
<label class="block text-xs font-semibold text-gray-600 mb-1">Penghasilan (Rp) <span class="text-red-500">*</span></label>
|
||||
<input type="text" id="penghasilan_display"
|
||||
class="input-field w-full border border-gray-200 rounded-xl px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent bg-gray-50"
|
||||
placeholder="0" required inputmode="decimal">
|
||||
placeholder="0" inputmode="numeric" autocomplete="off">
|
||||
<input type="hidden" name="penghasilan" id="penghasilan">
|
||||
<p class="error-msg text-xs text-red-500 mt-1 hidden">Penghasilan tidak boleh negatif.</p>
|
||||
</div>
|
||||
|
||||
<div class="field-group">
|
||||
<label class="block text-xs font-semibold text-gray-600 mb-1">
|
||||
Jumlah Tanggungan <span class="text-red-500">*</span>
|
||||
</label>
|
||||
<label class="block text-xs font-semibold text-gray-600 mb-1">Jumlah Tanggungan <span class="text-red-500">*</span></label>
|
||||
<input type="number" name="jumlah_tanggungan" id="jumlah_tanggungan" min="0"
|
||||
class="input-field w-full border border-gray-200 rounded-xl px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent bg-gray-50"
|
||||
placeholder="Jumlah orang" required inputmode="numeric">
|
||||
|
|
@ -254,16 +221,53 @@ class="input-field w-full border border-gray-200 rounded-xl px-3 py-2 text-sm fo
|
|||
</div>
|
||||
|
||||
<div class="field-group">
|
||||
<label class="block text-xs font-semibold text-gray-600 mb-1">
|
||||
Menerima Bantuan Lain? <span class="text-red-500">*</span>
|
||||
</label>
|
||||
<select name="bantuan_lain"
|
||||
<label class="block text-xs font-semibold text-gray-600 mb-1">Menerima Bantuan Lain? <span class="text-red-500">*</span></label>
|
||||
<select name="bantuan_lain" id="bantuan_lain"
|
||||
class="input-field w-full border border-gray-200 rounded-xl px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent bg-gray-50">
|
||||
<option value="tidak">Tidak</option>
|
||||
<option value="ya">Ya</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{-- Aset Kepemilikan — checkbox + manual --}}
|
||||
<div class="section-body px-5 pb-5">
|
||||
<label class="block text-xs font-semibold text-gray-600 mb-2">
|
||||
Aset Kepemilikan <span class="text-red-500">*</span>
|
||||
<span class="text-gray-400 font-normal ml-1">(centang semua yang dimiliki)</span>
|
||||
</label>
|
||||
|
||||
<div class="aset-checkbox-grid mb-3" id="aset-checkbox-grid">
|
||||
@php
|
||||
$asetOptions = [
|
||||
'motor' => 'Motor',
|
||||
'motor tua' => 'Motor Tua',
|
||||
'tanah' => 'Tanah',
|
||||
'emas' => 'Emas',
|
||||
'mobil' => 'Mobil',
|
||||
'rumah' => 'Rumah',
|
||||
'sepeda' => 'Sepeda',
|
||||
'ternak' => 'Ternak',
|
||||
];
|
||||
@endphp
|
||||
|
||||
@foreach($asetOptions as $val => $label)
|
||||
<label class="aset-checkbox-item">
|
||||
<input type="checkbox" name="aset_pilihan[]" value="{{ $val }}">
|
||||
<span>{{ $label }}</span>
|
||||
</label>
|
||||
@endforeach
|
||||
</div>
|
||||
|
||||
<div class="flex flex-col gap-1">
|
||||
<label class="text-xs text-gray-500">Aset lain (tulis manual, pisahkan dengan koma):</label>
|
||||
<input type="text" name="aset_manual" id="aset_manual"
|
||||
class="input-field w-full border border-gray-200 rounded-xl px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent bg-gray-50"
|
||||
placeholder="Contoh: sawah, kebun, mesin jahit">
|
||||
</div>
|
||||
|
||||
<p class="error-msg-aset text-xs text-red-500 mt-1 hidden">Aset kepemilikan wajib diisi (centang minimal satu atau isi kolom manual).</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{-- SECTION 4: Kondisi Tempat Tinggal --}}
|
||||
|
|
@ -273,38 +277,94 @@ class="input-field w-full border border-gray-200 rounded-xl px-3 py-2 text-sm fo
|
|||
<h3 class="text-sm font-semibold text-gray-700">Kondisi Tempat Tinggal</h3>
|
||||
</div>
|
||||
<div class="section-body p-5 grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
|
||||
|
||||
{{-- Lantai Rumah --}}
|
||||
<div class="field-group">
|
||||
<label class="block text-xs font-semibold text-gray-600 mb-1">
|
||||
Kondisi Rumah <span class="text-red-500">*</span>
|
||||
</label>
|
||||
<select name="kondisi_rumah" id="kondisi_rumah"
|
||||
<label class="block text-xs font-semibold text-gray-600 mb-1">Lantai Rumah <span class="text-red-500">*</span></label>
|
||||
<select name="lantai_rumah" id="lantai_rumah"
|
||||
class="input-field w-full border border-gray-200 rounded-xl px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent bg-gray-50">
|
||||
<option value="Layak">Layak</option>
|
||||
<option value="Sedang">Sedang</option>
|
||||
<option value="Tidak Layak">Tidak Layak</option>
|
||||
<option value="">-- Pilih --</option>
|
||||
<option value="Tanah">Tanah</option>
|
||||
<option value="Papan/Kayu">Papan / Kayu</option>
|
||||
<option value="Semen Kasar">Semen Kasar</option>
|
||||
<option value="Keramik Murah">Keramik Murah</option>
|
||||
<option value="Keramik">Keramik</option>
|
||||
</select>
|
||||
<p class="error-msg text-xs text-red-500 mt-1 hidden">Lantai rumah wajib dipilih.</p>
|
||||
</div>
|
||||
|
||||
{{-- Dinding Rumah --}}
|
||||
<div class="field-group">
|
||||
<label class="block text-xs font-semibold text-gray-600 mb-1">
|
||||
Meteran Listrik <span class="text-red-500">*</span>
|
||||
</label>
|
||||
<label class="block text-xs font-semibold text-gray-600 mb-1">Dinding Rumah <span class="text-red-500">*</span></label>
|
||||
<select name="dinding_rumah" id="dinding_rumah"
|
||||
class="input-field w-full border border-gray-200 rounded-xl px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent bg-gray-50">
|
||||
<option value="">-- Pilih --</option>
|
||||
<option value="Bambu">Bambu</option>
|
||||
<option value="Anyaman Bambu/Gedek">Anyaman Bambu / Gedek</option>
|
||||
<option value="Kayu/Papan">Kayu / Papan</option>
|
||||
<option value="Tembok Tidak Plester">Tembok Tidak Plester</option>
|
||||
<option value="Tembok Plester">Tembok Plester</option>
|
||||
</select>
|
||||
<p class="error-msg text-xs text-red-500 mt-1 hidden">Dinding rumah wajib dipilih.</p>
|
||||
</div>
|
||||
|
||||
{{-- Atap Rumah --}}
|
||||
<div class="field-group">
|
||||
<label class="block text-xs font-semibold text-gray-600 mb-1">Atap Rumah <span class="text-red-500">*</span></label>
|
||||
<select name="atap_rumah" id="atap_rumah"
|
||||
class="input-field w-full border border-gray-200 rounded-xl px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent bg-gray-50">
|
||||
<option value="">-- Pilih --</option>
|
||||
<option value="Bambu/Jerami">Bambu / Jerami</option>
|
||||
<option value="Seng">Seng</option>
|
||||
<option value="Campuran Seng+Genteng">Campuran Seng + Genteng</option>
|
||||
<option value="Genteng">Genteng</option>
|
||||
<option value="Genteng Beton">Genteng Beton</option>
|
||||
</select>
|
||||
<p class="error-msg text-xs text-red-500 mt-1 hidden">Atap rumah wajib dipilih.</p>
|
||||
</div>
|
||||
|
||||
<div class="field-group">
|
||||
<label class="block text-xs font-semibold text-gray-600 mb-1">Luas Rumah (m²) <span class="text-red-500">*</span></label>
|
||||
<input type="number" name="luas_rumah_m2" id="luas_rumah_m2" min="1"
|
||||
class="input-field w-full border border-gray-200 rounded-xl px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent bg-gray-50"
|
||||
placeholder="Contoh: 36" required inputmode="numeric">
|
||||
<p class="error-msg text-xs text-red-500 mt-1 hidden">Luas rumah wajib diisi.</p>
|
||||
</div>
|
||||
|
||||
<div class="field-group">
|
||||
<label class="block text-xs font-semibold text-gray-600 mb-1">Status Kepemilikan Rumah <span class="text-red-500">*</span></label>
|
||||
<select name="status_kepemilikan_rumah" id="status_kepemilikan_rumah"
|
||||
class="input-field w-full border border-gray-200 rounded-xl px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent bg-gray-50">
|
||||
<option value="">-- Pilih --</option>
|
||||
<option value="Menumpang">Menumpang</option>
|
||||
<option value="Sewa/Kontrak">Sewa / Kontrak</option>
|
||||
<option value="Milik Sendiri">Milik Sendiri</option>
|
||||
</select>
|
||||
<p class="error-msg text-xs text-red-500 mt-1 hidden">Status kepemilikan rumah wajib dipilih.</p>
|
||||
</div>
|
||||
|
||||
<div class="field-group">
|
||||
<label class="block text-xs font-semibold text-gray-600 mb-1">Meteran Listrik <span class="text-red-500">*</span></label>
|
||||
<select name="meteran_listrik" id="meteran_listrik"
|
||||
class="input-field w-full border border-gray-200 rounded-xl px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent bg-gray-50">
|
||||
<option value="450VA">450VA</option>
|
||||
<option value="900VA">900VA</option>
|
||||
<option value="1300VA+">1300VA+</option>
|
||||
<option value="">-- Pilih --</option>
|
||||
<option value="450VA">450 VA</option>
|
||||
<option value="900VA">900 VA</option>
|
||||
<option value="1300VA+">1300 VA ke atas</option>
|
||||
</select>
|
||||
<p class="error-msg text-xs text-red-500 mt-1 hidden">Meteran listrik wajib dipilih.</p>
|
||||
</div>
|
||||
|
||||
<div class="field-group">
|
||||
<label class="block text-xs font-semibold text-gray-600 mb-1">
|
||||
Sumber Air <span class="text-red-500">*</span>
|
||||
</label>
|
||||
<label class="block text-xs font-semibold text-gray-600 mb-1">Sumber Air <span class="text-red-500">*</span></label>
|
||||
<select name="sumber_air" id="sumber_air"
|
||||
class="input-field w-full border border-gray-200 rounded-xl px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent bg-gray-50">
|
||||
<option value="PDAM">PDAM</option>
|
||||
<option value="Sumur">Sumur</option>
|
||||
<option value="">-- Pilih --</option>
|
||||
<option value="Sungai">Sungai</option>
|
||||
<option value="Sumur">Sumur</option>
|
||||
<option value="PDAM">PDAM</option>
|
||||
</select>
|
||||
<p class="error-msg text-xs text-red-500 mt-1 hidden">Sumber air wajib dipilih.</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -318,48 +378,39 @@ class="input-field w-full border border-gray-200 rounded-xl px-3 py-2 text-sm fo
|
|||
<div class="section-body p-5 grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-4">
|
||||
<div class="field-group">
|
||||
<label class="block text-xs font-semibold text-gray-600 mb-1">Foto Rumah Depan</label>
|
||||
<input type="file" name="foto_rumah_depan" accept="image/*"
|
||||
class="input-field w-full border border-gray-200 rounded-xl px-3 py-2 text-sm bg-gray-50">
|
||||
<input type="file" name="foto_rumah_depan" accept="image/*" class="input-field w-full border border-gray-200 rounded-xl px-3 py-2 text-sm bg-gray-50">
|
||||
</div>
|
||||
<div class="field-group">
|
||||
<label class="block text-xs font-semibold text-gray-600 mb-1">Foto Rumah Belakang</label>
|
||||
<input type="file" name="foto_rumah_belakang" accept="image/*"
|
||||
class="input-field w-full border border-gray-200 rounded-xl px-3 py-2 text-sm bg-gray-50">
|
||||
<input type="file" name="foto_rumah_belakang" accept="image/*" class="input-field w-full border border-gray-200 rounded-xl px-3 py-2 text-sm bg-gray-50">
|
||||
</div>
|
||||
<div class="field-group">
|
||||
<label class="block text-xs font-semibold text-gray-600 mb-1">Foto Rumah Kanan</label>
|
||||
<input type="file" name="foto_rumah_kanan" accept="image/*"
|
||||
class="input-field w-full border border-gray-200 rounded-xl px-3 py-2 text-sm bg-gray-50">
|
||||
<input type="file" name="foto_rumah_kanan" accept="image/*" class="input-field w-full border border-gray-200 rounded-xl px-3 py-2 text-sm bg-gray-50">
|
||||
</div>
|
||||
<div class="field-group">
|
||||
<label class="block text-xs font-semibold text-gray-600 mb-1">Foto Rumah Kiri</label>
|
||||
<input type="file" name="foto_rumah_kiri" accept="image/*"
|
||||
class="input-field w-full border border-gray-200 rounded-xl px-3 py-2 text-sm bg-gray-50">
|
||||
<input type="file" name="foto_rumah_kiri" accept="image/*" class="input-field w-full border border-gray-200 rounded-xl px-3 py-2 text-sm bg-gray-50">
|
||||
</div>
|
||||
<div class="field-group">
|
||||
<label class="block text-xs font-semibold text-gray-600 mb-1">Foto KK</label>
|
||||
<input type="file" name="foto_kk" accept="image/*"
|
||||
class="input-field w-full border border-gray-200 rounded-xl px-3 py-2 text-sm bg-gray-50">
|
||||
<input type="file" name="foto_kk" accept="image/*" class="input-field w-full border border-gray-200 rounded-xl px-3 py-2 text-sm bg-gray-50">
|
||||
</div>
|
||||
<div class="field-group">
|
||||
<label class="block text-xs font-semibold text-gray-600 mb-1">Foto KTP</label>
|
||||
<input type="file" name="foto_ktp" accept="image/*"
|
||||
class="input-field w-full border border-gray-200 rounded-xl px-3 py-2 text-sm bg-gray-50">
|
||||
<input type="file" name="foto_ktp" accept="image/*" class="input-field w-full border border-gray-200 rounded-xl px-3 py-2 text-sm bg-gray-50">
|
||||
</div>
|
||||
<div class="field-group">
|
||||
<label class="block text-xs font-semibold text-gray-600 mb-1">Foto Rekening Listrik</label>
|
||||
<input type="file" name="foto_rekening_listrik" accept="image/*,.pdf"
|
||||
class="input-field w-full border border-gray-200 rounded-xl px-3 py-2 text-sm bg-gray-50">
|
||||
<input type="file" name="foto_rekening_listrik" accept="image/*,.pdf" class="input-field w-full border border-gray-200 rounded-xl px-3 py-2 text-sm bg-gray-50">
|
||||
</div>
|
||||
<div class="field-group">
|
||||
<label class="block text-xs font-semibold text-gray-600 mb-1">Foto Meteran Air</label>
|
||||
<input type="file" name="foto_meteran_air" accept="image/*"
|
||||
class="input-field w-full border border-gray-200 rounded-xl px-3 py-2 text-sm bg-gray-50">
|
||||
<input type="file" name="foto_meteran_air" accept="image/*" class="input-field w-full border border-gray-200 rounded-xl px-3 py-2 text-sm bg-gray-50">
|
||||
</div>
|
||||
<div class="field-group lg:col-span-2">
|
||||
<label class="block text-xs font-semibold text-gray-600 mb-1">Dokumen Pendukung Lainnya</label>
|
||||
<input type="file" name="dokumen_pendukung" accept="image/*,.pdf,.doc,.docx"
|
||||
class="input-field w-full border border-gray-200 rounded-xl px-3 py-2 text-sm bg-gray-50">
|
||||
<input type="file" name="dokumen_pendukung" accept="image/*,.pdf,.doc,.docx" class="input-field w-full border border-gray-200 rounded-xl px-3 py-2 text-sm bg-gray-50">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -370,9 +421,14 @@ class="input-field w-full border border-gray-200 rounded-xl px-3 py-2 text-sm bg
|
|||
class="px-5 py-2 text-sm font-medium bg-white border border-gray-200 text-gray-600 rounded-xl hover:bg-gray-50 transition">
|
||||
Batal
|
||||
</a>
|
||||
<button type="submit"
|
||||
<button type="submit" id="btnSimpan"
|
||||
style="display:inline-flex;align-items:center;gap:8px;"
|
||||
class="px-6 py-2 text-sm font-semibold bg-blue-600 text-white rounded-xl hover:bg-blue-700 transition shadow-md shadow-blue-200">
|
||||
Simpan Data
|
||||
<svg id="btnSpinner" style="display:none;width:16px;height:16px;animation:spin .7s linear infinite;flex-shrink:0;" fill="none" viewBox="0 0 24 24">
|
||||
<circle style="opacity:.25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"/>
|
||||
<path style="opacity:.75" fill="currentColor" d="M4 12a8 8 0 018-8v8z"/>
|
||||
</svg>
|
||||
<span id="btnText">Simpan Data</span>
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
|
|
@ -407,39 +463,56 @@ function showToast(msg) {
|
|||
setTimeout(() => t.classList.add('hidden'), 3500);
|
||||
}
|
||||
|
||||
// Validasi aset: minimal satu checkbox atau ada teks manual
|
||||
function validateAset() {
|
||||
const checked = document.querySelectorAll('input[name="aset_pilihan[]"]:checked').length > 0;
|
||||
const manual = (document.getElementById('aset_manual')?.value ?? '').trim() !== '';
|
||||
const errEl = document.querySelector('.error-msg-aset');
|
||||
if (!checked && !manual) {
|
||||
if (errEl) errEl.classList.remove('hidden');
|
||||
document.getElementById('aset-checkbox-grid')?.classList.add('ring-1', 'ring-red-400', 'rounded-xl');
|
||||
return false;
|
||||
}
|
||||
if (errEl) errEl.classList.add('hidden');
|
||||
document.getElementById('aset-checkbox-grid')?.classList.remove('ring-1', 'ring-red-400', 'rounded-xl');
|
||||
return true;
|
||||
}
|
||||
|
||||
document.querySelectorAll('input[name="aset_pilihan[]"]').forEach(cb => {
|
||||
cb.addEventListener('change', validateAset);
|
||||
});
|
||||
document.getElementById('aset_manual')?.addEventListener('input', validateAset);
|
||||
|
||||
const rules = {
|
||||
rt_id: v => v !== '' ? null : 'RT wajib dipilih.',
|
||||
nik: v => /^\d{16}$/.test(v) ? null : 'NIK harus tepat 16 digit angka.',
|
||||
no_kk: v => /^\d{16}$/.test(v) ? null : 'No. KK harus tepat 16 digit angka.',
|
||||
nama_lengkap: v => v.trim().length >= 3 ? null : 'Nama minimal 3 karakter.',
|
||||
tempat_lahir: v => v.trim().length >= 2 ? null : 'Tempat lahir wajib diisi.',
|
||||
tanggal_lahir: v => v !== '' ? null : 'Tanggal lahir wajib diisi.',
|
||||
usia: v => (parseInt(v) >= 0 && parseInt(v) <= 150) ? null : 'Usia harus antara 0–150.',
|
||||
status_perkawinan: v => v !== '' ? null : 'Status perkawinan wajib dipilih.',
|
||||
alamat: v => v.trim().length >= 5 ? null : 'Alamat minimal 5 karakter.',
|
||||
desa: v => v.trim().length >= 2 ? null : 'Dusun wajib diisi.',
|
||||
aset_kepemilikan: v => v.trim().length >= 2 ? null : 'Aset kepemilikan wajib diisi.',
|
||||
pekerjaan: v => v.trim().length >= 2 ? null : 'Pekerjaan wajib diisi.',
|
||||
penghasilan: v => parseFloat(v) >= 0 ? null : 'Penghasilan tidak boleh negatif.',
|
||||
jumlah_tanggungan: v => parseInt(v) >= 0 ? null : 'Jumlah tanggungan tidak boleh negatif.',
|
||||
rt_id: v => v !== '' ? null : 'RT wajib dipilih.',
|
||||
nik: v => /^\d{16}$/.test(v) ? null : 'NIK harus tepat 16 digit angka.',
|
||||
no_kk: v => /^\d{16}$/.test(v) ? null : 'No. KK harus tepat 16 digit angka.',
|
||||
nama_lengkap: v => v.trim().length >= 3 ? null : 'Nama minimal 3 karakter.',
|
||||
jenis_kelamin: v => v !== '' ? null : 'Jenis kelamin wajib dipilih.',
|
||||
tempat_lahir: v => v.trim().length >= 2 ? null : 'Tempat lahir wajib diisi.',
|
||||
tanggal_lahir: v => v !== '' ? null : 'Tanggal lahir wajib diisi.',
|
||||
usia: v => (parseInt(v) >= 0 && parseInt(v) <= 150) ? null : 'Usia harus antara 0–150.',
|
||||
status_perkawinan: v => v !== '' ? null : 'Status perkawinan wajib dipilih.',
|
||||
alamat: v => v.trim().length >= 5 ? null : 'Alamat minimal 5 karakter.',
|
||||
desa: v => v.trim().length >= 2 ? null : 'Dusun wajib diisi.',
|
||||
pekerjaan: v => v !== '' ? null : 'Pekerjaan wajib dipilih.',
|
||||
penghasilan: v => (v !== '' && parseFloat(v) >= 0) ? null : 'Penghasilan tidak boleh negatif.',
|
||||
jumlah_tanggungan: v => parseInt(v) >= 0 ? null : 'Jumlah tanggungan tidak boleh negatif.',
|
||||
lantai_rumah: v => v !== '' ? null : 'Lantai rumah wajib dipilih.',
|
||||
dinding_rumah: v => v !== '' ? null : 'Dinding rumah wajib dipilih.',
|
||||
atap_rumah: v => v !== '' ? null : 'Atap rumah wajib dipilih.',
|
||||
luas_rumah_m2: v => (parseInt(v) >= 1) ? null : 'Luas rumah wajib diisi.',
|
||||
status_kepemilikan_rumah: v => v !== '' ? null : 'Status kepemilikan rumah wajib dipilih.',
|
||||
meteran_listrik: v => v !== '' ? null : 'Meteran listrik wajib dipilih.',
|
||||
sumber_air: v => v !== '' ? null : 'Sumber air wajib dipilih.',
|
||||
};
|
||||
|
||||
Object.keys(rules).forEach(id => {
|
||||
const el = document.getElementById(id);
|
||||
if (!el) return;
|
||||
el.addEventListener('blur', () => {
|
||||
const err = rules[id](el.value);
|
||||
err ? showError(id, err) : clearError(id);
|
||||
});
|
||||
el.addEventListener('input', () => {
|
||||
if (!el.classList.contains('border-red-400')) return;
|
||||
const err = rules[id](el.value);
|
||||
err ? showError(id, err) : clearError(id);
|
||||
});
|
||||
el.addEventListener('change', () => {
|
||||
const err = rules[id](el.value);
|
||||
err ? showError(id, err) : clearError(id);
|
||||
});
|
||||
el.addEventListener('blur', () => { const e = rules[id](el.value); e ? showError(id, e) : clearError(id); });
|
||||
el.addEventListener('input', () => { if (!el.classList.contains('border-red-400')) return; const e = rules[id](el.value); e ? showError(id, e) : clearError(id); });
|
||||
el.addEventListener('change', () => { const e = rules[id](el.value); e ? showError(id, e) : clearError(id); });
|
||||
});
|
||||
|
||||
document.getElementById('tanggal_lahir')?.addEventListener('change', function () {
|
||||
|
|
@ -448,14 +521,31 @@ function showToast(msg) {
|
|||
let age = today.getFullYear() - dob.getFullYear();
|
||||
const m = today.getMonth() - dob.getMonth();
|
||||
if (m < 0 || (m === 0 && today.getDate() < dob.getDate())) age--;
|
||||
if (age >= 0 && age <= 150) {
|
||||
document.getElementById('usia').value = age;
|
||||
clearError('usia');
|
||||
}
|
||||
if (age >= 0 && age <= 150) { document.getElementById('usia').value = age; clearError('usia'); }
|
||||
});
|
||||
|
||||
const displayInput = document.getElementById('penghasilan_display');
|
||||
const hiddenInput = document.getElementById('penghasilan');
|
||||
if (displayInput && hiddenInput) {
|
||||
displayInput.addEventListener('input', function () {
|
||||
let raw = this.value.replace(/\D/g, '');
|
||||
this.value = raw ? parseInt(raw).toLocaleString('id-ID') : '';
|
||||
hiddenInput.value = raw;
|
||||
const e = rules['penghasilan'](hiddenInput.value);
|
||||
e ? showError('penghasilan', e) : clearError('penghasilan');
|
||||
});
|
||||
displayInput.addEventListener('blur', function () {
|
||||
const e = rules['penghasilan'](hiddenInput.value);
|
||||
e ? showError('penghasilan', e) : clearError('penghasilan');
|
||||
});
|
||||
}
|
||||
|
||||
document.getElementById('formPendataan').addEventListener('submit', function (e) {
|
||||
if (displayInput && hiddenInput && hiddenInput.value === '') {
|
||||
hiddenInput.value = displayInput.value.replace(/\D/g, '');
|
||||
}
|
||||
let valid = true;
|
||||
|
||||
Object.keys(rules).forEach(id => {
|
||||
const el = document.getElementById(id);
|
||||
if (!el) return;
|
||||
|
|
@ -463,12 +553,23 @@ function showToast(msg) {
|
|||
if (err) { showError(id, err); valid = false; }
|
||||
else clearError(id);
|
||||
});
|
||||
|
||||
// Validasi aset
|
||||
if (!validateAset()) valid = false;
|
||||
|
||||
if (!valid) {
|
||||
e.preventDefault();
|
||||
showToast('Mohon perbaiki field yang tidak valid sebelum menyimpan.');
|
||||
const firstErr = document.querySelector('.border-red-400');
|
||||
firstErr?.scrollIntoView({ behavior: 'smooth', block: 'center' });
|
||||
document.querySelector('.border-red-400, .ring-red-400')?.scrollIntoView({ behavior: 'smooth', block: 'center' });
|
||||
return;
|
||||
}
|
||||
|
||||
const btn = document.getElementById('btnSimpan');
|
||||
btn.disabled = true;
|
||||
btn.style.opacity = '0.75';
|
||||
btn.style.cursor = 'not-allowed';
|
||||
document.getElementById('btnSpinner').style.display = 'block';
|
||||
document.getElementById('btnText').textContent = 'Memproses & Memprediksi...';
|
||||
});
|
||||
</script>
|
||||
</x-app-layout>
|
||||
|
|
@ -16,11 +16,9 @@ class="inline-flex items-center gap-1.5 px-4 py-2 bg-white border border-gray-20
|
|||
</x-slot>
|
||||
|
||||
<style>
|
||||
/* Cegah auto-zoom iOS */
|
||||
.input-field { font-size: 16px !important; }
|
||||
@media (min-width: 640px) { .input-field { font-size: 14px !important; } }
|
||||
|
||||
/* Grid section: 1 kolom di mobile, 2 di md, 4 di lg */
|
||||
.section-grid {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr;
|
||||
|
|
@ -34,15 +32,12 @@ class="inline-flex items-center gap-1.5 px-4 py-2 bg-white border border-gray-20
|
|||
.section-grid { grid-template-columns: repeat(4, 1fr); padding: 20px; }
|
||||
}
|
||||
|
||||
/* span 2 kolom hanya ketika grid sudah 2+ kolom */
|
||||
.col-span-2-up { grid-column: span 1; }
|
||||
@media (min-width: 640px) { .col-span-2-up { grid-column: span 2; } }
|
||||
|
||||
/* Padding section card lebih kecil di mobile */
|
||||
.section-head { padding: 10px 16px; }
|
||||
@media (min-width: 640px) { .section-head { padding: 10px 20px; } }
|
||||
|
||||
/* Tombol aksi full-width di mobile */
|
||||
.action-row {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
|
|
@ -54,9 +49,17 @@ class="inline-flex items-center gap-1.5 px-4 py-2 bg-white border border-gray-20
|
|||
.action-row a,
|
||||
.action-row button { width: 100%; justify-content: center; text-align: center; }
|
||||
}
|
||||
|
||||
@keyframes spin { to { transform: rotate(360deg); } }
|
||||
|
||||
.aset-checkbox-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(160px, 1fr)); gap: 8px; }
|
||||
.aset-checkbox-item { display: flex; align-items: center; gap: 8px; padding: 8px 12px;
|
||||
border: 1px solid #e5e7eb; border-radius: 10px; cursor: pointer; transition: background .15s, border-color .15s; }
|
||||
.aset-checkbox-item:has(input:checked) { background: #eff6ff; border-color: #93c5fd; }
|
||||
.aset-checkbox-item input[type=checkbox] { accent-color: #2563eb; width: 15px; height: 15px; flex-shrink: 0; }
|
||||
.aset-checkbox-item span { font-size: 13px; color: #374151; }
|
||||
</style>
|
||||
|
||||
{{-- ERROR VALIDASI --}}
|
||||
@if ($errors->any())
|
||||
<div class="mb-4 flex items-start gap-3 rounded-xl border border-red-200 bg-red-50 px-4 py-3 text-sm text-red-700">
|
||||
<svg class="w-4 h-4 shrink-0 mt-0.5 text-red-500" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
|
|
@ -73,7 +76,7 @@ class="inline-flex items-center gap-1.5 px-4 py-2 bg-white border border-gray-20
|
|||
</div>
|
||||
@endif
|
||||
|
||||
<form action="{{ route('rt.calon-penerima.update', $calonPenerima->id) }}" method="POST" id="formEditPendataan" novalidate>
|
||||
<form action="{{ route('rt.calon-penerima.update', $calonPenerima->id) }}" method="POST" id="formEditPendataan" novalidate enctype="multipart/form-data">
|
||||
@csrf
|
||||
@method('PUT')
|
||||
<input type="hidden" name="rt_id" value="{{ $calonPenerima->rt_id }}">
|
||||
|
|
@ -91,8 +94,7 @@ class="inline-flex items-center gap-1.5 px-4 py-2 bg-white border border-gray-20
|
|||
<input type="text"
|
||||
value="RT {{ str_pad($calonPenerima->rt->nomor_rt ?? 0, 3, '0', STR_PAD_LEFT) }}"
|
||||
class="w-full border border-gray-200 rounded-xl px-3 py-2 bg-gray-100 text-gray-700 cursor-not-allowed"
|
||||
style="font-size:16px;"
|
||||
readonly>
|
||||
style="font-size:16px;" readonly>
|
||||
<p class="text-[11px] text-gray-400 mt-1">RT mengikuti akun RT yang sedang login.</p>
|
||||
</div>
|
||||
|
||||
|
|
@ -196,25 +198,15 @@ class="input-field w-full border border-gray-200 rounded-xl px-3 py-2 focus:outl
|
|||
<input type="text" name="desa" id="desa"
|
||||
value="{{ old('desa', $calonPenerima->desa) }}"
|
||||
class="w-full border border-gray-200 rounded-xl px-3 py-2 bg-gray-100 text-gray-700 cursor-not-allowed"
|
||||
style="font-size:16px;"
|
||||
readonly>
|
||||
style="font-size:16px;" readonly>
|
||||
<p class="text-[11px] text-gray-400 mt-1">Dusun mengikuti RT dan tidak dapat diubah.</p>
|
||||
</div>
|
||||
|
||||
<div class="field-group">
|
||||
<label class="block text-xs font-semibold text-gray-600 mb-1">Aset Kepemilikan <span class="text-red-500">*</span></label>
|
||||
<input type="text" name="aset_kepemilikan" id="aset_kepemilikan"
|
||||
value="{{ old('aset_kepemilikan', $calonPenerima->aset_kepemilikan) }}"
|
||||
class="input-field w-full border border-gray-200 rounded-xl px-3 py-2 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent bg-gray-50"
|
||||
placeholder="Misal: Rumah, Motor, dll" required>
|
||||
<p class="error-msg text-xs text-red-500 mt-1 hidden">Aset kepemilikan wajib diisi.</p>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{-- SECTION 3: Data Ekonomi --}}
|
||||
<div class="bg-white rounded-2xl shadow-sm border border-gray-100 mb-5 overflow-hidden">
|
||||
<div class="bg-white rounded-2xl shadow-sm border border-gray-100 mb-4 overflow-hidden">
|
||||
<div class="section-head border-b border-gray-100 flex items-center gap-2 bg-gray-50">
|
||||
<div class="w-1 h-4 bg-amber-500 rounded-full"></div>
|
||||
<h3 class="text-sm font-semibold text-gray-700">Data Ekonomi</h3>
|
||||
|
|
@ -223,19 +215,24 @@ class="input-field w-full border border-gray-200 rounded-xl px-3 py-2 focus:outl
|
|||
|
||||
<div class="field-group">
|
||||
<label class="block text-xs font-semibold text-gray-600 mb-1">Pekerjaan <span class="text-red-500">*</span></label>
|
||||
<input type="text" name="pekerjaan" id="pekerjaan"
|
||||
value="{{ old('pekerjaan', $calonPenerima->pekerjaan) }}"
|
||||
class="input-field w-full border border-gray-200 rounded-xl px-3 py-2 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent bg-gray-50"
|
||||
placeholder="Jenis pekerjaan" required>
|
||||
<p class="error-msg text-xs text-red-500 mt-1 hidden">Pekerjaan wajib diisi.</p>
|
||||
<select name="pekerjaan" id="pekerjaan"
|
||||
class="input-field w-full border border-gray-200 rounded-xl px-3 py-2 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent bg-gray-50">
|
||||
<option value="">-- Pilih --</option>
|
||||
@foreach(['Tidak Bekerja','Buruh Harian Lepas','Petani Kecil','Mengurus Rumah Tangga','Pedagang','Wiraswasta','Karyawan Swasta','Guru Honorer','PNS','TNI/Polri'] as $pkj)
|
||||
<option value="{{ $pkj }}" {{ old('pekerjaan', $calonPenerima->pekerjaan) === $pkj ? 'selected' : '' }}>{{ $pkj }}</option>
|
||||
@endforeach
|
||||
</select>
|
||||
<p class="error-msg text-xs text-red-500 mt-1 hidden">Pekerjaan wajib dipilih.</p>
|
||||
</div>
|
||||
|
||||
<div class="field-group">
|
||||
<label class="block text-xs font-semibold text-gray-600 mb-1">Penghasilan (Rp) <span class="text-red-500">*</span></label>
|
||||
<input type="number" step="0.01" name="penghasilan" id="penghasilan" min="0"
|
||||
value="{{ old('penghasilan', $calonPenerima->penghasilan) }}"
|
||||
<input type="text" id="penghasilan_display"
|
||||
value="{{ old('penghasilan', $calonPenerima->penghasilan) ? number_format((float) old('penghasilan', $calonPenerima->penghasilan), 0, ',', '.') : '' }}"
|
||||
class="input-field w-full border border-gray-200 rounded-xl px-3 py-2 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent bg-gray-50"
|
||||
placeholder="0" required inputmode="decimal">
|
||||
placeholder="0" inputmode="numeric" autocomplete="off">
|
||||
<input type="hidden" name="penghasilan" id="penghasilan"
|
||||
value="{{ old('penghasilan', $calonPenerima->penghasilan) }}">
|
||||
<p class="error-msg text-xs text-red-500 mt-1 hidden">Penghasilan tidak boleh negatif.</p>
|
||||
</div>
|
||||
|
||||
|
|
@ -258,6 +255,197 @@ class="input-field w-full border border-gray-200 rounded-xl px-3 py-2 focus:outl
|
|||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
{{-- Aset Kepemilikan --}}
|
||||
<div style="padding: 0 16px 16px;">
|
||||
@php
|
||||
$asetOptions = ['motor'=>'Motor','motor tua'=>'Motor Tua','tanah'=>'Tanah','emas'=>'Emas','mobil'=>'Mobil','rumah'=>'Rumah','sepeda'=>'Sepeda','ternak'=>'Ternak'];
|
||||
$asetLama = strtolower(old('aset_kepemilikan', $calonPenerima->aset_kepemilikan ?? ''));
|
||||
$asetLamaArr = array_map('trim', explode(',', $asetLama));
|
||||
$asetManualVal = '';
|
||||
$asetChecked = [];
|
||||
foreach ($asetLamaArr as $item) {
|
||||
if (array_key_exists($item, $asetOptions)) {
|
||||
$asetChecked[] = $item;
|
||||
} else if ($item !== '' && $item !== 'tidak punya') {
|
||||
$asetManualVal .= ($asetManualVal ? ', ' : '') . $item;
|
||||
}
|
||||
}
|
||||
@endphp
|
||||
<label class="block text-xs font-semibold text-gray-600 mb-2">
|
||||
Aset Kepemilikan <span class="text-red-500">*</span>
|
||||
<span class="text-gray-400 font-normal ml-1">(centang semua yang dimiliki)</span>
|
||||
</label>
|
||||
<div class="aset-checkbox-grid mb-3" id="aset-checkbox-grid">
|
||||
@foreach($asetOptions as $val => $label)
|
||||
<label class="aset-checkbox-item">
|
||||
<input type="checkbox" name="aset_pilihan[]" value="{{ $val }}"
|
||||
{{ in_array($val, $asetChecked) ? 'checked' : '' }}>
|
||||
<span>{{ $label }}</span>
|
||||
</label>
|
||||
@endforeach
|
||||
</div>
|
||||
<div class="flex flex-col gap-1">
|
||||
<label class="text-xs text-gray-500">Aset lain (tulis manual, pisahkan dengan koma):</label>
|
||||
<input type="text" name="aset_manual" id="aset_manual"
|
||||
value="{{ $asetManualVal }}"
|
||||
class="input-field w-full border border-gray-200 rounded-xl px-3 py-2 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent bg-gray-50"
|
||||
placeholder="Contoh: sawah, kebun, mesin jahit">
|
||||
</div>
|
||||
<p class="error-msg-aset text-xs text-red-500 mt-1 hidden">Aset kepemilikan wajib diisi.</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{-- SECTION 4: Kondisi Tempat Tinggal --}}
|
||||
<div class="bg-white rounded-2xl shadow-sm border border-gray-100 mb-4 overflow-hidden">
|
||||
<div class="section-head border-b border-gray-100 flex items-center gap-2 bg-gray-50">
|
||||
<div class="w-1 h-4 bg-purple-500 rounded-full"></div>
|
||||
<h3 class="text-sm font-semibold text-gray-700">Kondisi Tempat Tinggal</h3>
|
||||
</div>
|
||||
<div class="section-grid" style="grid-template-columns: repeat(auto-fill, minmax(160px, 1fr));">
|
||||
|
||||
<div class="field-group">
|
||||
<label class="block text-xs font-semibold text-gray-600 mb-1">Lantai Rumah <span class="text-red-500">*</span></label>
|
||||
<select name="lantai_rumah" id="lantai_rumah"
|
||||
class="input-field w-full border border-gray-200 rounded-xl px-3 py-2 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent bg-gray-50">
|
||||
<option value="">-- Pilih --</option>
|
||||
@foreach(['Tanah','Papan/Kayu','Semen Kasar','Keramik Murah','Keramik'] as $opt)
|
||||
<option value="{{ $opt }}" {{ old('lantai_rumah', $calonPenerima->lantai_rumah) === $opt ? 'selected' : '' }}>{{ $opt }}</option>
|
||||
@endforeach
|
||||
</select>
|
||||
<p class="error-msg text-xs text-red-500 mt-1 hidden">Lantai rumah wajib dipilih.</p>
|
||||
</div>
|
||||
|
||||
<div class="field-group">
|
||||
<label class="block text-xs font-semibold text-gray-600 mb-1">Dinding Rumah <span class="text-red-500">*</span></label>
|
||||
<select name="dinding_rumah" id="dinding_rumah"
|
||||
class="input-field w-full border border-gray-200 rounded-xl px-3 py-2 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent bg-gray-50">
|
||||
<option value="">-- Pilih --</option>
|
||||
@foreach(['Bambu','Anyaman Bambu/Gedek','Kayu/Papan','Tembok Tidak Plester','Tembok Plester'] as $opt)
|
||||
<option value="{{ $opt }}" {{ old('dinding_rumah', $calonPenerima->dinding_rumah) === $opt ? 'selected' : '' }}>{{ $opt }}</option>
|
||||
@endforeach
|
||||
</select>
|
||||
<p class="error-msg text-xs text-red-500 mt-1 hidden">Dinding rumah wajib dipilih.</p>
|
||||
</div>
|
||||
|
||||
<div class="field-group">
|
||||
<label class="block text-xs font-semibold text-gray-600 mb-1">Atap Rumah <span class="text-red-500">*</span></label>
|
||||
<select name="atap_rumah" id="atap_rumah"
|
||||
class="input-field w-full border border-gray-200 rounded-xl px-3 py-2 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent bg-gray-50">
|
||||
<option value="">-- Pilih --</option>
|
||||
@foreach(['Bambu/Jerami','Seng','Campuran Seng+Genteng','Genteng','Genteng Beton'] as $opt)
|
||||
<option value="{{ $opt }}" {{ old('atap_rumah', $calonPenerima->atap_rumah) === $opt ? 'selected' : '' }}>{{ $opt }}</option>
|
||||
@endforeach
|
||||
</select>
|
||||
<p class="error-msg text-xs text-red-500 mt-1 hidden">Atap rumah wajib dipilih.</p>
|
||||
</div>
|
||||
|
||||
<div class="field-group">
|
||||
<label class="block text-xs font-semibold text-gray-600 mb-1">Luas Rumah (m²) <span class="text-red-500">*</span></label>
|
||||
<input type="number" name="luas_rumah_m2" id="luas_rumah_m2" min="1"
|
||||
value="{{ old('luas_rumah_m2', $calonPenerima->luas_rumah_m2) }}"
|
||||
class="input-field w-full border border-gray-200 rounded-xl px-3 py-2 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent bg-gray-50"
|
||||
placeholder="Contoh: 36" required inputmode="numeric">
|
||||
<p class="error-msg text-xs text-red-500 mt-1 hidden">Luas rumah wajib diisi.</p>
|
||||
</div>
|
||||
|
||||
<div class="field-group">
|
||||
<label class="block text-xs font-semibold text-gray-600 mb-1">Status Kepemilikan Rumah <span class="text-red-500">*</span></label>
|
||||
<select name="status_kepemilikan_rumah" id="status_kepemilikan_rumah"
|
||||
class="input-field w-full border border-gray-200 rounded-xl px-3 py-2 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent bg-gray-50">
|
||||
<option value="">-- Pilih --</option>
|
||||
@foreach(['Menumpang','Sewa/Kontrak','Milik Sendiri'] as $opt)
|
||||
<option value="{{ $opt }}" {{ old('status_kepemilikan_rumah', $calonPenerima->status_kepemilikan_rumah) === $opt ? 'selected' : '' }}>{{ $opt }}</option>
|
||||
@endforeach
|
||||
</select>
|
||||
<p class="error-msg text-xs text-red-500 mt-1 hidden">Status kepemilikan rumah wajib dipilih.</p>
|
||||
</div>
|
||||
|
||||
<div class="field-group">
|
||||
<label class="block text-xs font-semibold text-gray-600 mb-1">Meteran Listrik <span class="text-red-500">*</span></label>
|
||||
<select name="meteran_listrik" id="meteran_listrik"
|
||||
class="input-field w-full border border-gray-200 rounded-xl px-3 py-2 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent bg-gray-50">
|
||||
<option value="">-- Pilih --</option>
|
||||
@foreach(['450VA'=>'450 VA','900VA'=>'900 VA','1300VA+'=>'1300 VA ke atas'] as $val => $lbl)
|
||||
<option value="{{ $val }}" {{ old('meteran_listrik', $calonPenerima->meteran_listrik) === $val ? 'selected' : '' }}>{{ $lbl }}</option>
|
||||
@endforeach
|
||||
</select>
|
||||
<p class="error-msg text-xs text-red-500 mt-1 hidden">Meteran listrik wajib dipilih.</p>
|
||||
</div>
|
||||
|
||||
<div class="field-group">
|
||||
<label class="block text-xs font-semibold text-gray-600 mb-1">Sumber Air <span class="text-red-500">*</span></label>
|
||||
<select name="sumber_air" id="sumber_air"
|
||||
class="input-field w-full border border-gray-200 rounded-xl px-3 py-2 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent bg-gray-50">
|
||||
<option value="">-- Pilih --</option>
|
||||
@foreach(['Sungai','Sumur','PDAM'] as $opt)
|
||||
<option value="{{ $opt }}" {{ old('sumber_air', $calonPenerima->sumber_air) === $opt ? 'selected' : '' }}>{{ $opt }}</option>
|
||||
@endforeach
|
||||
</select>
|
||||
<p class="error-msg text-xs text-red-500 mt-1 hidden">Sumber air wajib dipilih.</p>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{-- SECTION 5: Upload Foto --}}
|
||||
<div class="bg-white rounded-2xl shadow-sm border border-gray-100 mb-5 overflow-hidden">
|
||||
<div class="section-head border-b border-gray-100 flex items-center gap-2 bg-gray-50">
|
||||
<div class="w-1 h-4 bg-rose-500 rounded-full"></div>
|
||||
<h3 class="text-sm font-semibold text-gray-700">Upload Dokumen & Foto</h3>
|
||||
<span class="text-xs text-gray-400 font-normal">(kosongkan jika tidak ingin mengganti)</span>
|
||||
</div>
|
||||
<div class="section-grid" style="grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));">
|
||||
|
||||
@php
|
||||
$fotoFields = [
|
||||
'foto_rumah_depan' => 'Foto Rumah Depan',
|
||||
'foto_rumah_belakang' => 'Foto Rumah Belakang',
|
||||
'foto_rumah_kanan' => 'Foto Rumah Kanan',
|
||||
'foto_rumah_kiri' => 'Foto Rumah Kiri',
|
||||
'foto_kk' => 'Foto KK',
|
||||
'foto_ktp' => 'Foto KTP',
|
||||
'foto_meteran_air' => 'Foto Meteran Air',
|
||||
];
|
||||
@endphp
|
||||
|
||||
@foreach($fotoFields as $field => $label)
|
||||
<div class="field-group">
|
||||
<label class="block text-xs font-semibold text-gray-600 mb-1">{{ $label }}</label>
|
||||
@if($calonPenerima->$field)
|
||||
<div class="mb-1">
|
||||
<img src="{{ asset('storage/' . $calonPenerima->$field) }}"
|
||||
class="w-full h-24 object-cover rounded-lg border border-gray-200">
|
||||
</div>
|
||||
@endif
|
||||
<input type="file" name="{{ $field }}" accept="image/*"
|
||||
class="input-field w-full border border-gray-200 rounded-xl px-3 py-2 text-sm bg-gray-50">
|
||||
</div>
|
||||
@endforeach
|
||||
|
||||
<div class="field-group">
|
||||
<label class="block text-xs font-semibold text-gray-600 mb-1">Foto Rekening Listrik</label>
|
||||
@if($calonPenerima->foto_rekening_listrik)
|
||||
<div class="mb-1 text-xs text-blue-600">
|
||||
<a href="{{ asset('storage/' . $calonPenerima->foto_rekening_listrik) }}" target="_blank">Lihat file saat ini</a>
|
||||
</div>
|
||||
@endif
|
||||
<input type="file" name="foto_rekening_listrik" accept="image/*,.pdf"
|
||||
class="input-field w-full border border-gray-200 rounded-xl px-3 py-2 text-sm bg-gray-50">
|
||||
</div>
|
||||
|
||||
<div class="field-group">
|
||||
<label class="block text-xs font-semibold text-gray-600 mb-1">Dokumen Pendukung Lainnya</label>
|
||||
@if($calonPenerima->dokumen_pendukung)
|
||||
<div class="mb-1 text-xs text-blue-600">
|
||||
<a href="{{ asset('storage/' . $calonPenerima->dokumen_pendukung) }}" target="_blank">Lihat file saat ini</a>
|
||||
</div>
|
||||
@endif
|
||||
<input type="file" name="dokumen_pendukung" accept="image/*,.pdf,.doc,.docx"
|
||||
class="input-field w-full border border-gray-200 rounded-xl px-3 py-2 text-sm bg-gray-50">
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{-- ACTIONS --}}
|
||||
|
|
@ -266,12 +454,16 @@ class="input-field w-full border border-gray-200 rounded-xl px-3 py-2 focus:outl
|
|||
class="inline-flex items-center gap-1.5 px-5 py-2 text-sm font-medium bg-white border border-gray-200 text-gray-600 rounded-xl hover:bg-gray-50 transition">
|
||||
Batal
|
||||
</a>
|
||||
<button type="submit"
|
||||
class="inline-flex items-center gap-2 px-6 py-2 text-sm font-semibold bg-blue-600 text-white rounded-xl hover:bg-blue-700 transition shadow-md shadow-blue-200">
|
||||
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 13l4 4L19 7"/>
|
||||
<button type="submit" id="btnSimpan"
|
||||
style="display:inline-flex;align-items:center;gap:8px;"
|
||||
class="px-6 py-2 text-sm font-semibold bg-blue-600 text-white rounded-xl hover:bg-blue-700 transition shadow-md shadow-blue-200">
|
||||
<svg id="btnSpinner"
|
||||
style="display:none;width:16px;height:16px;animation:spin .7s linear infinite;flex-shrink:0;"
|
||||
fill="none" viewBox="0 0 24 24">
|
||||
<circle style="opacity:.25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"/>
|
||||
<path style="opacity:.75" fill="currentColor" d="M4 12a8 8 0 018-8v8z"/>
|
||||
</svg>
|
||||
Simpan Perubahan
|
||||
<span id="btnText">Simpan Perubahan</span>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
|
|
@ -307,20 +499,43 @@ function showToast(msg) {
|
|||
setTimeout(() => t.classList.add('hidden'), 3500);
|
||||
}
|
||||
|
||||
function validateAset() {
|
||||
const checked = document.querySelectorAll('input[name="aset_pilihan[]"]:checked').length > 0;
|
||||
const manual = (document.getElementById('aset_manual')?.value ?? '').trim() !== '';
|
||||
const errEl = document.querySelector('.error-msg-aset');
|
||||
if (!checked && !manual) {
|
||||
if (errEl) errEl.classList.remove('hidden');
|
||||
document.getElementById('aset-checkbox-grid')?.classList.add('ring-1', 'ring-red-400', 'rounded-xl');
|
||||
return false;
|
||||
}
|
||||
if (errEl) errEl.classList.add('hidden');
|
||||
document.getElementById('aset-checkbox-grid')?.classList.remove('ring-1', 'ring-red-400', 'rounded-xl');
|
||||
return true;
|
||||
}
|
||||
|
||||
document.querySelectorAll('input[name="aset_pilihan[]"]').forEach(cb => cb.addEventListener('change', validateAset));
|
||||
document.getElementById('aset_manual')?.addEventListener('input', validateAset);
|
||||
|
||||
const rules = {
|
||||
nik: v => /^\d{16}$/.test(v) ? null : 'NIK harus tepat 16 digit angka.',
|
||||
no_kk: v => /^\d{16}$/.test(v) ? null : 'No. KK harus tepat 16 digit angka.',
|
||||
nama_lengkap: v => v.trim().length >= 3 ? null : 'Nama minimal 3 karakter.',
|
||||
tempat_lahir: v => v.trim().length >= 2 ? null : 'Tempat lahir wajib diisi.',
|
||||
tanggal_lahir: v => v !== '' ? null : 'Tanggal lahir wajib diisi.',
|
||||
usia: v => (parseInt(v) >= 17 && parseInt(v) <= 100) ? null : 'Usia harus antara 17–100.',
|
||||
status_perkawinan: v => v !== '' ? null : 'Status perkawinan wajib dipilih.',
|
||||
alamat: v => v.trim().length >= 5 ? null : 'Alamat minimal 5 karakter.',
|
||||
desa: v => v.trim().length >= 2 ? null : 'Dusun wajib diisi.',
|
||||
aset_kepemilikan: v => v.trim().length >= 2 ? null : 'Aset kepemilikan wajib diisi.',
|
||||
pekerjaan: v => v.trim().length >= 2 ? null : 'Pekerjaan wajib diisi.',
|
||||
penghasilan: v => parseFloat(v) >= 0 ? null : 'Penghasilan tidak boleh negatif.',
|
||||
jumlah_tanggungan: v => parseInt(v) >= 0 ? null : 'Jumlah tanggungan tidak boleh negatif.',
|
||||
nik: v => /^\d{16}$/.test(v) ? null : 'NIK harus tepat 16 digit angka.',
|
||||
no_kk: v => /^\d{16}$/.test(v) ? null : 'No. KK harus tepat 16 digit angka.',
|
||||
nama_lengkap: v => v.trim().length >= 3 ? null : 'Nama minimal 3 karakter.',
|
||||
tempat_lahir: v => v.trim().length >= 2 ? null : 'Tempat lahir wajib diisi.',
|
||||
tanggal_lahir: v => v !== '' ? null : 'Tanggal lahir wajib diisi.',
|
||||
usia: v => (parseInt(v) >= 17 && parseInt(v) <= 100) ? null : 'Usia harus antara 17–100.',
|
||||
status_perkawinan: v => v !== '' ? null : 'Status perkawinan wajib dipilih.',
|
||||
alamat: v => v.trim().length >= 5 ? null : 'Alamat minimal 5 karakter.',
|
||||
desa: v => v.trim().length >= 2 ? null : 'Dusun wajib diisi.',
|
||||
pekerjaan: v => v !== '' ? null : 'Pekerjaan wajib dipilih.',
|
||||
penghasilan: v => (v !== '' && parseFloat(v) >= 0) ? null : 'Penghasilan tidak boleh negatif.',
|
||||
jumlah_tanggungan: v => parseInt(v) >= 0 ? null : 'Jumlah tanggungan tidak boleh negatif.',
|
||||
lantai_rumah: v => v !== '' ? null : 'Lantai rumah wajib dipilih.',
|
||||
dinding_rumah: v => v !== '' ? null : 'Dinding rumah wajib dipilih.',
|
||||
atap_rumah: v => v !== '' ? null : 'Atap rumah wajib dipilih.',
|
||||
luas_rumah_m2: v => parseInt(v) >= 1 ? null : 'Luas rumah wajib diisi.',
|
||||
status_kepemilikan_rumah: v => v !== '' ? null : 'Status kepemilikan rumah wajib dipilih.',
|
||||
meteran_listrik: v => v !== '' ? null : 'Meteran listrik wajib dipilih.',
|
||||
sumber_air: v => v !== '' ? null : 'Sumber air wajib dipilih.',
|
||||
};
|
||||
|
||||
Object.keys(rules).forEach(id => {
|
||||
|
|
@ -340,13 +555,29 @@ function showToast(msg) {
|
|||
let age = today.getFullYear() - dob.getFullYear();
|
||||
const m = today.getMonth() - dob.getMonth();
|
||||
if (m < 0 || (m === 0 && today.getDate() < dob.getDate())) age--;
|
||||
if (age >= 0 && age <= 150) {
|
||||
document.getElementById('usia').value = age;
|
||||
clearError('usia');
|
||||
}
|
||||
if (age >= 0 && age <= 150) { document.getElementById('usia').value = age; clearError('usia'); }
|
||||
});
|
||||
|
||||
const displayInput = document.getElementById('penghasilan_display');
|
||||
const hiddenInput = document.getElementById('penghasilan');
|
||||
if (displayInput && hiddenInput) {
|
||||
displayInput.addEventListener('input', function () {
|
||||
let raw = this.value.replace(/\D/g, '');
|
||||
this.value = raw ? parseInt(raw).toLocaleString('id-ID') : '';
|
||||
hiddenInput.value = raw;
|
||||
const err = rules['penghasilan'](hiddenInput.value);
|
||||
err ? showError('penghasilan', err) : clearError('penghasilan');
|
||||
});
|
||||
displayInput.addEventListener('blur', function () {
|
||||
const err = rules['penghasilan'](hiddenInput.value);
|
||||
err ? showError('penghasilan', err) : clearError('penghasilan');
|
||||
});
|
||||
}
|
||||
|
||||
document.getElementById('formEditPendataan').addEventListener('submit', function (e) {
|
||||
if (displayInput && hiddenInput && hiddenInput.value === '') {
|
||||
hiddenInput.value = displayInput.value.replace(/\D/g, '');
|
||||
}
|
||||
let valid = true;
|
||||
Object.keys(rules).forEach(id => {
|
||||
const el = document.getElementById(id);
|
||||
|
|
@ -355,11 +586,19 @@ function showToast(msg) {
|
|||
if (err) { showError(id, err); valid = false; }
|
||||
else clearError(id);
|
||||
});
|
||||
if (!validateAset()) valid = false;
|
||||
if (!valid) {
|
||||
e.preventDefault();
|
||||
showToast('Mohon perbaiki field yang tidak valid sebelum menyimpan.');
|
||||
document.querySelector('.border-red-400')?.scrollIntoView({ behavior: 'smooth', block: 'center' });
|
||||
document.querySelector('.border-red-400, .ring-red-400')?.scrollIntoView({ behavior: 'smooth', block: 'center' });
|
||||
return;
|
||||
}
|
||||
const btn = document.getElementById('btnSimpan');
|
||||
btn.disabled = true;
|
||||
btn.style.opacity = '0.75';
|
||||
btn.style.cursor = 'not-allowed';
|
||||
document.getElementById('btnSpinner').style.display = 'block';
|
||||
document.getElementById('btnText').textContent = 'Menyimpan...';
|
||||
});
|
||||
</script>
|
||||
</x-app-layout>
|
||||
|
|
@ -6,24 +6,19 @@
|
|||
<p style="font-size:11px;color:#9ca3af;margin-top:2px;">Daftar seluruh warga yang telah didaftarkan</p>
|
||||
</div>
|
||||
<a href="{{ route('rt.calon-penerima.create') }}"
|
||||
style="display:inline-flex;align-items:center;gap:7px;padding:8px 16px;background:#2563eb;color:#fff;font-size:12px;font-weight:700;border-radius:11px;text-decoration:none;box-shadow:0 3px 10px rgba(37,99,235,.28);">
|
||||
<svg width="14" height="14" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
style="display:inline-flex;align-items:center;gap:7px;padding:10px 20px;background:#2563eb;color:#fff;font-size:13px;font-weight:700;border-radius:12px;text-decoration:none;box-shadow:0 3px 10px rgba(37,99,235,.28);">
|
||||
<svg width="15" height="15" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2.5" d="M12 4v16m8-8H4"/>
|
||||
</svg>
|
||||
Tambah Data
|
||||
Tambah Data Warga
|
||||
</a>
|
||||
</div>
|
||||
</x-slot>
|
||||
|
||||
<style>
|
||||
/* Flash */
|
||||
.flash{display:flex;align-items:center;gap:10px;border-radius:12px;padding:9px 14px;margin-bottom:10px;font-size:12px;font-weight:500;}
|
||||
.flash svg{width:14px;height:14px;flex-shrink:0;}
|
||||
|
||||
/* Card */
|
||||
.idx-card{background:#fff;border-radius:18px;border:1.5px solid #f1f5f9;box-shadow:0 1px 4px rgba(0,0,0,.04),0 4px 16px rgba(0,0,0,.03);overflow:hidden;}
|
||||
|
||||
/* ── Desktop Table ── */
|
||||
.tbl-wrap{overflow-x:auto;-webkit-overflow-scrolling:touch;}
|
||||
table.idx-tbl{width:100%;border-collapse:collapse;min-width:680px;}
|
||||
table.idx-tbl thead tr{background:#f8fafc;border-bottom:1.5px solid #f1f5f9;}
|
||||
|
|
@ -32,52 +27,44 @@
|
|||
table.idx-tbl tbody tr:hover{background:#f0f7ff;}
|
||||
table.idx-tbl tbody tr:last-child{border-bottom:none;}
|
||||
table.idx-tbl tbody td{padding:11px 14px;vertical-align:middle;}
|
||||
|
||||
/* ── Mobile Card List ── */
|
||||
.mob-list{display:none;flex-direction:column;gap:10px;padding:14px;}
|
||||
@media(max-width:640px){
|
||||
.tbl-wrap{display:none;}
|
||||
.mob-list{display:flex;}
|
||||
}
|
||||
.mob-card{background:#fafbfc;border:1.5px solid #f1f5f9;border-radius:14px;padding:13px;}
|
||||
.mob-list{display:none;flex-direction:column;gap:12px;padding:14px;}
|
||||
@media(max-width:640px){.tbl-wrap{display:none;}.mob-list{display:flex;}}
|
||||
.mob-card{background:#fafbfc;border:1.5px solid #f1f5f9;border-radius:16px;padding:14px;}
|
||||
.mob-top{display:flex;align-items:center;gap:10px;margin-bottom:10px;}
|
||||
.mob-name{font-size:13px;font-weight:800;color:#111827;flex:1;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;}
|
||||
.mob-name{font-size:14px;font-weight:800;color:#111827;flex:1;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;}
|
||||
.mob-sub{font-size:10.5px;color:#94a3b8;margin-top:1px;font-family:monospace;}
|
||||
.mob-row{display:flex;justify-content:space-between;align-items:center;gap:8px;flex-wrap:wrap;margin-bottom:10px;}
|
||||
.mob-actions{display:flex;gap:6px;flex-wrap:wrap;padding-top:10px;border-top:1px solid #f1f5f9;}
|
||||
.mob-actions .btn-act{flex:1;justify-content:center;}
|
||||
|
||||
/* Avatar */
|
||||
.av{width:32px;height:32px;border-radius:10px;display:flex;align-items:center;justify-content:center;font-size:12px;font-weight:800;color:#fff;flex-shrink:0;background:linear-gradient(135deg,#3b82f6,#2563eb);}
|
||||
|
||||
/* Badges */
|
||||
.badge{display:inline-flex;align-items:center;gap:4px;padding:3px 9px;border-radius:20px;font-size:10.5px;font-weight:700;white-space:nowrap;}
|
||||
.dot{width:5px;height:5px;border-radius:50%;flex-shrink:0;}
|
||||
.badge-rt{display:inline-flex;align-items:center;padding:2px 8px;border-radius:7px;background:#eff6ff;color:#1d4ed8;font-size:11px;font-weight:700;border:1px solid #bfdbfe;}
|
||||
|
||||
.trk-draft {background:#f3f4f6;color:#374151;} .trk-draft .dot{background:#9ca3af;}
|
||||
.trk-terkirim {background:#eff6ff;color:#1d4ed8;} .trk-terkirim .dot{background:#3b82f6;}
|
||||
.trk-validasi {background:#fffbeb;color:#b45309;} .trk-validasi .dot{background:#f59e0b;}
|
||||
.trk-selesai {background:#f0fdf4;color:#166534;} .trk-selesai .dot{background:#22c55e;}
|
||||
.st-pending {background:#fffbeb;color:#b45309;} .st-pending .dot{background:#f59e0b;}
|
||||
.st-disetujui {background:#f0fdf4;color:#166534;} .st-disetujui .dot{background:#22c55e;}
|
||||
.st-ditolak {background:#fff1f2;color:#9f1239;} .st-ditolak .dot{background:#f43f5e;}
|
||||
|
||||
/* Action buttons */
|
||||
.btn-act{display:inline-flex;align-items:center;gap:4px;padding:5px 10px;border-radius:8px;font-size:11px;font-weight:600;text-decoration:none;border:none;cursor:pointer;white-space:nowrap;line-height:1.4;}
|
||||
.btn-act svg{width:11px;height:11px;flex-shrink:0;}
|
||||
.btn-detail{background:#f3f4f6;color:#374151;} .btn-detail:hover{background:#e5e7eb;}
|
||||
.btn-edit {background:#eff6ff;color:#1d4ed8;} .btn-edit:hover{background:#dbeafe;}
|
||||
.btn-ajukan{background:#f0fdf4;color:#166534;} .btn-ajukan:hover{background:#dcfce7;}
|
||||
.btn-hapus {background:#fff1f2;color:#9f1239;} .btn-hapus:hover{background:#ffe4e6;}
|
||||
|
||||
/* Empty state */
|
||||
.mob-actions{display:flex;gap:8px;flex-wrap:wrap;padding-top:10px;border-top:1px solid #f1f5f9;}
|
||||
.mob-actions .btn-act{flex:1;justify-content:center;padding:9px 10px;font-size:12px;}
|
||||
.av{width:36px;height:36px;border-radius:10px;display:flex;align-items:center;justify-content:center;font-size:13px;font-weight:800;color:#fff;flex-shrink:0;background:linear-gradient(135deg,#3b82f6,#2563eb);}
|
||||
.badge{display:inline-flex;align-items:center;gap:4px;padding:4px 10px;border-radius:20px;font-size:11px;font-weight:700;white-space:nowrap;}
|
||||
.dot{width:6px;height:6px;border-radius:50%;flex-shrink:0;}
|
||||
.badge-rt{display:inline-flex;align-items:center;padding:3px 9px;border-radius:7px;background:#eff6ff;color:#1d4ed8;font-size:11px;font-weight:700;border:1px solid #bfdbfe;}
|
||||
.trk-draft{background:#f3f4f6;color:#374151;}.trk-draft .dot{background:#9ca3af;}
|
||||
.trk-terkirim{background:#eff6ff;color:#1d4ed8;}.trk-terkirim .dot{background:#3b82f6;}
|
||||
.trk-validasi{background:#fffbeb;color:#b45309;}.trk-validasi .dot{background:#f59e0b;}
|
||||
.trk-selesai{background:#f0fdf4;color:#166534;}.trk-selesai .dot{background:#22c55e;}
|
||||
.trk-ditinjau{background:#f5f3ff;color:#6d28d9;border:1px solid #ddd6fe;}.trk-ditinjau .dot{background:#7c3aed;}
|
||||
.st-pending{background:#fffbeb;color:#b45309;}.st-pending .dot{background:#f59e0b;}
|
||||
.st-disetujui{background:#f0fdf4;color:#166534;}.st-disetujui .dot{background:#22c55e;}
|
||||
.st-ditolak{background:#fff1f2;color:#9f1239;}.st-ditolak .dot{background:#f43f5e;}
|
||||
.btn-act{display:inline-flex;align-items:center;gap:5px;padding:7px 12px;border-radius:9px;font-size:12px;font-weight:700;text-decoration:none;border:none;cursor:pointer;white-space:nowrap;line-height:1.4;}
|
||||
.btn-act svg{width:13px;height:13px;flex-shrink:0;}
|
||||
.btn-detail{background:#2563eb;color:#fff;box-shadow:0 2px 8px rgba(37,99,235,.25);}
|
||||
.btn-detail:hover{background:#1d4ed8;}
|
||||
.btn-edit{background:#eff6ff;color:#1d4ed8;}.btn-edit:hover{background:#dbeafe;}
|
||||
.btn-ajukan{background:#f0fdf4;color:#166534;}.btn-ajukan:hover{background:#dcfce7;}
|
||||
.btn-hapus{background:#fff1f2;color:#9f1239;}.btn-hapus:hover{background:#ffe4e6;}
|
||||
.empty-state{padding:48px 16px;text-align:center;}
|
||||
.empty-icon{width:44px;height:44px;background:#f3f4f6;border-radius:50%;display:flex;align-items:center;justify-content:center;margin:0 auto 10px;}
|
||||
.empty-icon svg{width:20px;height:20px;stroke:#d1d5db;}
|
||||
|
||||
/* Pagination */
|
||||
.pagi{padding:10px 16px;background:#fafafa;border-top:1px solid #f1f5f9;}
|
||||
.hasil-hint{display:flex;align-items:center;gap:8px;background:#fef9c3;border:1.5px solid #fde047;border-radius:10px;padding:8px 12px;margin-top:6px;}
|
||||
.hasil-hint svg{width:14px;height:14px;stroke:#ca8a04;flex-shrink:0;}
|
||||
.hasil-hint span{font-size:11px;font-weight:700;color:#92400e;}
|
||||
.btn-detail-selesai{background:linear-gradient(135deg,#10b981,#059669);color:#fff;box-shadow:0 3px 10px rgba(16,185,129,.3);animation:glow 2s ease-in-out infinite;}
|
||||
.btn-detail-selesai:hover{filter:brightness(1.1);}
|
||||
@keyframes glow{0%,100%{box-shadow:0 3px 10px rgba(16,185,129,.3);}50%{box-shadow:0 3px 18px rgba(16,185,129,.6);}}
|
||||
</style>
|
||||
|
||||
{{-- FLASH --}}
|
||||
|
|
@ -97,11 +84,11 @@
|
|||
<div class="idx-card">
|
||||
|
||||
{{-- HEADER BAR --}}
|
||||
<div style="padding:10px 16px;border-bottom:1px solid #f1f5f9;background:#fafafa;display:flex;align-items:center;justify-content:space-between;gap:12px;flex-wrap:wrap;">
|
||||
<div style="padding:12px 16px;border-bottom:1px solid #f1f5f9;background:#fafafa;display:flex;align-items:center;justify-content:space-between;gap:12px;flex-wrap:wrap;">
|
||||
<div style="display:flex;align-items:center;gap:8px;">
|
||||
<div style="width:3px;height:16px;background:#2563eb;border-radius:4px;"></div>
|
||||
<span style="font-size:12.5px;font-weight:700;color:#374151;">
|
||||
Total <span style="color:#2563eb;">{{ $calonPenerimas->total() }}</span> data
|
||||
<span style="font-size:13px;font-weight:700;color:#374151;">
|
||||
Total <span style="color:#2563eb;">{{ $calonPenerimas->total() }}</span> data terdaftar
|
||||
</span>
|
||||
</div>
|
||||
<span style="font-size:11px;color:#9ca3af;">
|
||||
|
|
@ -109,7 +96,7 @@
|
|||
</span>
|
||||
</div>
|
||||
|
||||
{{-- ── Desktop: Tabel ── --}}
|
||||
{{-- ── DESKTOP TABLE ── --}}
|
||||
<div class="tbl-wrap">
|
||||
<table class="idx-tbl">
|
||||
<thead>
|
||||
|
|
@ -137,59 +124,78 @@
|
|||
<span style="font-size:13px;font-weight:600;color:#111827;">{{ $item->nama_lengkap }}</span>
|
||||
</div>
|
||||
</td>
|
||||
<td style="font-family:monospace;font-size:11.5px;color:#6b7280;letter-spacing:.02em;">{{ $item->nik }}</td>
|
||||
<td style="font-family:monospace;font-size:11.5px;color:#6b7280;">{{ $item->nik }}</td>
|
||||
<td><span class="badge-rt">RT {{ str_pad($item->rt->nomor_rt ?? '-', 3, '0', STR_PAD_LEFT) }}</span></td>
|
||||
<td style="font-size:12px;color:#6b7280;">{{ $item->rt->dusun->nama_dusun ?? '-' }}</td>
|
||||
<td>
|
||||
@if($trk === 'draft')
|
||||
<span class="badge trk-draft"><span class="dot"></span>Draft</span>
|
||||
@elseif($trk === 'terkirim')
|
||||
<span class="badge trk-terkirim"><span class="dot"></span>Terkirim</span>
|
||||
@elseif($trk === 'sedang_validasi')
|
||||
<span class="badge trk-validasi"><span class="dot"></span>Validasi</span>
|
||||
@elseif($trk === 'selesai')
|
||||
<span class="badge trk-selesai"><span class="dot"></span>Selesai</span>
|
||||
@else
|
||||
<span class="badge trk-draft"><span class="dot"></span>—</span>
|
||||
@if($trk==='draft') <span class="badge trk-draft"><span class="dot"></span>Draft</span>
|
||||
@elseif($trk==='terkirim') <span class="badge trk-terkirim"><span class="dot"></span>Terkirim</span>
|
||||
@elseif($trk==='sedang_validasi') <span class="badge trk-validasi"><span class="dot"></span>Validasi</span>
|
||||
@elseif($trk==='selesai') <span class="badge trk-selesai"><span class="dot"></span>Selesai</span>
|
||||
@elseif($trk==='ditinjau_ulang') <span class="badge trk-ditinjau"><span class="dot"></span>Ditinjau Ulang</span>
|
||||
@else <span class="badge trk-draft"><span class="dot"></span>—</span>
|
||||
@endif
|
||||
</td>
|
||||
<td>
|
||||
@if($item->status_verifikasi === 'pending')
|
||||
<span class="badge st-pending"><span class="dot"></span>Pending</span>
|
||||
@elseif($item->status_verifikasi === 'disetujui')
|
||||
<span class="badge st-disetujui"><span class="dot"></span>Disetujui</span>
|
||||
@else
|
||||
<span class="badge st-ditolak"><span class="dot"></span>Ditolak</span>
|
||||
@if($item->status_verifikasi==='pending') <span class="badge st-pending"><span class="dot"></span>Pending</span>
|
||||
@elseif($item->status_verifikasi==='disetujui') <span class="badge st-disetujui"><span class="dot"></span>Disetujui</span>
|
||||
@else <span class="badge st-ditolak"><span class="dot"></span>Ditolak</span>
|
||||
@endif
|
||||
</td>
|
||||
<td>
|
||||
<div style="display:flex;align-items:center;gap:5px;flex-wrap:wrap;">
|
||||
<a href="{{ route('rt.calon-penerima.show', $item->id) }}" class="btn-act btn-detail">
|
||||
<svg fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 12a3 3 0 11-6 0 3 3 0 016 0z"/><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M2.458 12C3.732 7.943 7.523 5 12 5c4.478 0 8.268 2.943 9.542 7-1.274 4.057-5.064 7-9.542 7-4.477 0-8.268-2.943-9.542-7z"/></svg>
|
||||
Detail
|
||||
</a>
|
||||
@if($trk === 'draft')
|
||||
<a href="{{ route('rt.calon-penerima.edit', $item->id) }}" class="btn-act btn-edit">
|
||||
<svg fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M11 5H6a2 2 0 00-2 2v11a2 2 0 002 2h11a2 2 0 002-2v-5m-1.414-9.414a2 2 0 112.828 2.828L11.828 15H9v-2.828l8.586-8.586z"/></svg>
|
||||
Edit
|
||||
<div style="display:flex;flex-direction:column;gap:5px;align-items:flex-start;">
|
||||
<div style="display:flex;align-items:center;gap:5px;flex-wrap:wrap;">
|
||||
|
||||
<a href="{{ route('rt.calon-penerima.show', $item->id) }}"
|
||||
class="btn-act {{ in_array($trk,['selesai','sedang_validasi']) ? 'btn-detail-selesai' : 'btn-detail' }}">
|
||||
<svg fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 12a3 3 0 11-6 0 3 3 0 016 0z"/><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M2.458 12C3.732 7.943 7.523 5 12 5c4.478 0 8.268 2.943 9.542 7-1.274 4.057-5.064 7-9.542 7-4.477 0-8.268-2.943-9.542-7z"/></svg>
|
||||
Lihat Detail
|
||||
</a>
|
||||
<form method="POST" action="{{ route('rt.calon-penerima.ajukan', $item->id) }}"
|
||||
onsubmit="return confirm('Yakin ajukan data {{ addslashes($item->nama_lengkap) }}? Setelah diajukan data tidak bisa diubah.')">
|
||||
@csrf @method('PATCH')
|
||||
<button type="submit" class="btn-act btn-ajukan">
|
||||
<svg fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z"/></svg>
|
||||
Ajukan
|
||||
</button>
|
||||
</form>
|
||||
<form method="POST" action="{{ route('rt.calon-penerima.destroy', $item->id) }}"
|
||||
onsubmit="return confirm('Yakin hapus data {{ addslashes($item->nama_lengkap) }}?')">
|
||||
@csrf @method('DELETE')
|
||||
<button type="submit" class="btn-act btn-hapus">
|
||||
<svg fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16"/></svg>
|
||||
Hapus
|
||||
</button>
|
||||
</form>
|
||||
|
||||
@if($trk==='draft')
|
||||
<a href="{{ route('rt.calon-penerima.edit', $item->id) }}" class="btn-act btn-edit">
|
||||
<svg fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M11 5H6a2 2 0 00-2 2v11a2 2 0 002 2h11a2 2 0 002-2v-5m-1.414-9.414a2 2 0 112.828 2.828L11.828 15H9v-2.828l8.586-8.586z"/></svg>
|
||||
Edit
|
||||
</a>
|
||||
<form method="POST" action="{{ route('rt.calon-penerima.ajukan', $item->id) }}"
|
||||
onsubmit="return confirm('Yakin ajukan data {{ addslashes($item->nama_lengkap) }}? Setelah diajukan data tidak bisa diubah.')">
|
||||
@csrf @method('PATCH')
|
||||
<button type="submit" class="btn-act btn-ajukan">
|
||||
<svg fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z"/></svg>
|
||||
Ajukan
|
||||
</button>
|
||||
</form>
|
||||
<form method="POST" action="{{ route('rt.calon-penerima.destroy', $item->id) }}"
|
||||
onsubmit="return confirm('Yakin hapus data {{ addslashes($item->nama_lengkap) }}?')">
|
||||
@csrf @method('DELETE')
|
||||
<button type="submit" class="btn-act btn-hapus">
|
||||
<svg fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16"/></svg>
|
||||
Hapus
|
||||
</button>
|
||||
</form>
|
||||
@endif
|
||||
</div>
|
||||
|
||||
{{-- Hint teks di bawah tombol --}}
|
||||
@if($trk==='selesai')
|
||||
<div class="hasil-hint">
|
||||
<svg fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 16h-1v-4h-1m1-4h.01M12 3a9 9 0 100 18A9 9 0 0012 3z"/></svg>
|
||||
<span>Hasil sudah ada — klik Lihat Detail</span>
|
||||
</div>
|
||||
@elseif($trk==='sedang_validasi')
|
||||
<div class="hasil-hint" style="background:#eff6ff;border-color:#bfdbfe;">
|
||||
<svg fill="none" stroke="currentColor" viewBox="0 0 24 24" style="stroke:#2563eb;"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 8v4l3 3m6-3a9 9 0 11-18 0 9 9 0 0118 0z"/></svg>
|
||||
<span style="color:#1e40af;">Sedang diproses admin</span>
|
||||
</div>
|
||||
@elseif($trk==='ditinjau_ulang')
|
||||
<div class="hasil-hint" style="background:#f5f3ff;border-color:#ddd6fe;">
|
||||
<svg fill="none" stroke="#7c3aed" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 12a3 3 0 11-6 0 3 3 0 016 0z"/><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M2.458 12C3.732 7.943 7.523 5 12 5c4.478 0 8.268 2.943 9.542 7-1.274 4.057-5.064 7-9.542 7-4.477 0-8.268-2.943-9.542-7z"/></svg>
|
||||
<span style="color:#6d28d9;">Data sedang ditinjau ulang oleh admin</span>
|
||||
</div>
|
||||
@elseif($trk==='draft')
|
||||
<div style="font-size:10.5px;color:#94a3b8;margin-top:2px;">← Cek detail dulu, lalu ajukan</div>
|
||||
@endif
|
||||
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
|
|
@ -210,7 +216,7 @@
|
|||
</table>
|
||||
</div>
|
||||
|
||||
{{-- ── Mobile: Card List (< 640px) ── --}}
|
||||
{{-- ── MOBILE CARD LIST ── --}}
|
||||
<div class="mob-list">
|
||||
@forelse($calonPenerimas as $index => $item)
|
||||
@php $trk = $item->tracking_status ?? 'draft'; @endphp
|
||||
|
|
@ -221,38 +227,66 @@
|
|||
<div class="mob-name">{{ $item->nama_lengkap }}</div>
|
||||
<div class="mob-sub">{{ $item->nik }}</div>
|
||||
</div>
|
||||
<span style="font-size:10px;color:#94a3b8;font-weight:700;">
|
||||
#{{ $calonPenerimas->firstItem() + $index }}
|
||||
</span>
|
||||
<span style="font-size:10px;color:#94a3b8;font-weight:700;">#{{ $calonPenerimas->firstItem() + $index }}</span>
|
||||
</div>
|
||||
|
||||
<div class="mob-row">
|
||||
<span class="badge-rt">RT {{ str_pad($item->rt->nomor_rt ?? '-', 3, '0', STR_PAD_LEFT) }}</span>
|
||||
<span style="font-size:11px;color:#6b7280;">{{ $item->rt->dusun->nama_dusun ?? '-' }}</span>
|
||||
@if($trk === 'draft')
|
||||
<span class="badge trk-draft"><span class="dot"></span>Draft</span>
|
||||
@elseif($trk === 'terkirim')
|
||||
<span class="badge trk-terkirim"><span class="dot"></span>Terkirim</span>
|
||||
@elseif($trk === 'sedang_validasi')
|
||||
<span class="badge trk-validasi"><span class="dot"></span>Validasi</span>
|
||||
@elseif($trk === 'selesai')
|
||||
<span class="badge trk-selesai"><span class="dot"></span>Selesai</span>
|
||||
@if($trk==='draft') <span class="badge trk-draft"><span class="dot"></span>Draft</span>
|
||||
@elseif($trk==='terkirim') <span class="badge trk-terkirim"><span class="dot"></span>Terkirim</span>
|
||||
@elseif($trk==='sedang_validasi') <span class="badge trk-validasi"><span class="dot"></span>Sedang Diproses</span>
|
||||
@elseif($trk==='selesai') <span class="badge trk-selesai"><span class="dot"></span>Selesai</span>
|
||||
@elseif($trk==='ditinjau_ulang') <span class="badge trk-ditinjau"><span class="dot"></span>Ditinjau Ulang</span>
|
||||
@endif
|
||||
@if($item->status_verifikasi === 'pending')
|
||||
<span class="badge st-pending"><span class="dot"></span>Pending</span>
|
||||
@elseif($item->status_verifikasi === 'disetujui')
|
||||
<span class="badge st-disetujui"><span class="dot"></span>Disetujui</span>
|
||||
@else
|
||||
<span class="badge st-ditolak"><span class="dot"></span>Ditolak</span>
|
||||
@if($item->status_verifikasi==='pending') <span class="badge st-pending"><span class="dot"></span>Pending</span>
|
||||
@elseif($item->status_verifikasi==='disetujui') <span class="badge st-disetujui"><span class="dot"></span>Disetujui ✓</span>
|
||||
@else <span class="badge st-ditolak"><span class="dot"></span>Ditolak</span>
|
||||
@endif
|
||||
</div>
|
||||
|
||||
{{-- Kotak info kontekstual --}}
|
||||
@if($trk==='selesai')
|
||||
<div style="background:#fef9c3;border:1.5px solid #fde047;border-radius:10px;padding:10px 12px;margin-bottom:10px;display:flex;align-items:center;gap:8px;">
|
||||
<span style="font-size:20px;">📋</span>
|
||||
<div>
|
||||
<div style="font-size:12px;font-weight:800;color:#92400e;">Hasil sudah keluar!</div>
|
||||
<div style="font-size:11px;color:#a16207;margin-top:1px;">Tekan tombol <b>Lihat Detail</b> di bawah untuk melihat hasilnya</div>
|
||||
</div>
|
||||
</div>
|
||||
@elseif($trk==='sedang_validasi')
|
||||
<div style="background:#eff6ff;border:1.5px solid #bfdbfe;border-radius:10px;padding:10px 12px;margin-bottom:10px;display:flex;align-items:center;gap:8px;">
|
||||
<span style="font-size:20px;">⏳</span>
|
||||
<div>
|
||||
<div style="font-size:12px;font-weight:800;color:#1e40af;">Sedang diproses admin</div>
|
||||
<div style="font-size:11px;color:#3b82f6;margin-top:1px;">Mohon tunggu, hasil akan muncul setelah admin selesai</div>
|
||||
</div>
|
||||
</div>
|
||||
@elseif($trk==='ditinjau_ulang')
|
||||
<div style="background:#f5f3ff;border:1.5px solid #ddd6fe;border-radius:10px;padding:10px 12px;margin-bottom:10px;display:flex;align-items:center;gap:8px;">
|
||||
<span style="font-size:20px;">🔍</span>
|
||||
<div>
|
||||
<div style="font-size:12px;font-weight:800;color:#6d28d9;">Data sedang ditinjau ulang</div>
|
||||
<div style="font-size:11px;color:#7c3aed;margin-top:1px;">Admin perangkat desa sedang meninjau kembali data ini</div>
|
||||
</div>
|
||||
</div>
|
||||
@elseif($trk==='draft')
|
||||
<div style="background:#f0fdf4;border:1.5px solid #bbf7d0;border-radius:10px;padding:10px 12px;margin-bottom:10px;display:flex;align-items:center;gap:8px;">
|
||||
<span style="font-size:20px;">👆</span>
|
||||
<div>
|
||||
<div style="font-size:12px;font-weight:800;color:#166534;">Cek dulu, lalu ajukan</div>
|
||||
<div style="font-size:11px;color:#16a34a;margin-top:1px;">Tekan <b>Lihat Detail</b> untuk cek data, lalu tekan <b>Ajukan</b></div>
|
||||
</div>
|
||||
</div>
|
||||
@endif
|
||||
|
||||
<div class="mob-actions">
|
||||
<a href="{{ route('rt.calon-penerima.show', $item->id) }}" class="btn-act btn-detail">
|
||||
<a href="{{ route('rt.calon-penerima.show', $item->id) }}"
|
||||
class="btn-act {{ in_array($trk,['selesai','sedang_validasi']) ? 'btn-detail-selesai' : 'btn-detail' }}">
|
||||
<svg fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 12a3 3 0 11-6 0 3 3 0 016 0z"/><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M2.458 12C3.732 7.943 7.523 5 12 5c4.478 0 8.268 2.943 9.542 7-1.274 4.057-5.064 7-9.542 7-4.477 0-8.268-2.943-9.542-7z"/></svg>
|
||||
Detail
|
||||
Lihat Detail
|
||||
</a>
|
||||
@if($trk === 'draft')
|
||||
@if($trk==='draft')
|
||||
<a href="{{ route('rt.calon-penerima.edit', $item->id) }}" class="btn-act btn-edit">
|
||||
<svg fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M11 5H6a2 2 0 00-2 2v11a2 2 0 002 2h11a2 2 0 002-2v-5m-1.414-9.414a2 2 0 112.828 2.828L11.828 15H9v-2.828l8.586-8.586z"/></svg>
|
||||
Edit
|
||||
|
|
@ -289,9 +323,7 @@
|
|||
@endforelse
|
||||
</div>
|
||||
|
||||
<div class="pagi">
|
||||
{{ $calonPenerimas->links() }}
|
||||
</div>
|
||||
<div class="pagi">{{ $calonPenerimas->links() }}</div>
|
||||
|
||||
</div>
|
||||
|
||||
|
|
|
|||
|
|
@ -1,553 +1,506 @@
|
|||
<x-app-layout>
|
||||
<x-slot name="header">
|
||||
<div style="display:flex; align-items:center; justify-content:space-between; gap:12px; flex-wrap:wrap;">
|
||||
<div style="display:flex; align-items:center; gap:10px; min-width:0;">
|
||||
<a href="{{ route('rt.calon-penerima.index') }}"
|
||||
style="width:32px; height:32px; display:flex; align-items:center; justify-content:center; border-radius:10px; background:#fff; border:1.5px solid #e5e7eb; color:#6b7280; text-decoration:none; flex-shrink:0;">
|
||||
<svg width="14" height="14" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10 19l-7-7m0 0l7-7m-7 7h18"/>
|
||||
</svg>
|
||||
</a>
|
||||
<div style="min-width:0;">
|
||||
<h2 style="font-size:16px; font-weight:800; color:#111827; line-height:1.2; white-space:nowrap; overflow:hidden; text-overflow:ellipsis;">Detail Calon Penerima</h2>
|
||||
<p style="font-size:11px; color:#9ca3af; margin-top:2px;">Data pendataan warga & hasil prediksi kelayakan</p>
|
||||
</div>
|
||||
<div class="flex items-center justify-between">
|
||||
<div>
|
||||
<h2 class="font-semibold text-xl text-gray-800 leading-tight">Detail Calon Penerima</h2>
|
||||
<p class="text-sm text-gray-500 mt-0.5">Informasi lengkap & hasil prediksi kelayakan.</p>
|
||||
</div>
|
||||
@if(($calonPenerima->tracking_status ?? 'draft') === 'draft')
|
||||
<a href="{{ route('rt.calon-penerima.edit', $calonPenerima->id) }}"
|
||||
style="display:inline-flex; align-items:center; gap:6px; padding:7px 14px; background:#2563eb; color:#fff; font-size:12px; font-weight:700; border-radius:10px; text-decoration:none; box-shadow:0 2px 8px rgba(37,99,235,.25); flex-shrink:0;">
|
||||
<svg width="13" height="13" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M11 5H6a2 2 0 00-2 2v11a2 2 0 002 2h11a2 2 0 002-2v-5m-1.414-9.414a2 2 0 112.828 2.828L11.828 15H9v-2.828l8.586-8.586z"/>
|
||||
</svg>
|
||||
Edit Data
|
||||
</a>
|
||||
@endif
|
||||
<a href="{{ route('rt.calon-penerima.index') }}"
|
||||
class="inline-flex items-center gap-2 px-4 py-2 text-sm font-medium bg-white border border-gray-200 text-gray-600 rounded-xl hover:bg-gray-50 transition">
|
||||
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 19l-7-7 7-7"/>
|
||||
</svg>
|
||||
Kembali
|
||||
</a>
|
||||
</div>
|
||||
</x-slot>
|
||||
|
||||
<style>
|
||||
/* ── CARDS ── */
|
||||
.sp-card{background:#fff;border-radius:16px;border:1.5px solid #f1f5f9;box-shadow:0 1px 3px rgba(0,0,0,.04);overflow:hidden;}
|
||||
.sp-head{padding:9px 16px;border-bottom:1px solid #f1f5f9;background:#fafafa;display:flex;align-items:center;gap:8px;}
|
||||
.sp-head .bar{width:3px;height:14px;border-radius:4px;flex-shrink:0;}
|
||||
.sp-head h3{font-size:10.5px;font-weight:700;color:#6b7280;text-transform:uppercase;letter-spacing:.07em;}
|
||||
.sp-body{padding:13px 16px;}
|
||||
|
||||
/* ── LABELS / VALUES ── */
|
||||
.lbl{font-size:10px;color:#9ca3af;text-transform:uppercase;letter-spacing:.07em;margin-bottom:2px;}
|
||||
.val{font-size:12.5px;font-weight:600;color:#111827;}
|
||||
|
||||
/* ── GRIDS ── */
|
||||
.g2{display:grid;grid-template-columns:1fr 1fr;gap:12px;}
|
||||
.g3{display:grid;grid-template-columns:repeat(3,1fr);gap:12px;}
|
||||
.g4{display:grid;grid-template-columns:repeat(4,1fr);gap:12px;}
|
||||
|
||||
/* ── PILLS ── */
|
||||
.pill{display:inline-flex;align-items:center;gap:5px;padding:4px 11px;border-radius:20px;font-size:11px;font-weight:700;}
|
||||
.dot{width:6px;height:6px;border-radius:50%;flex-shrink:0;}
|
||||
|
||||
/* ── FEATURE LIST ── */
|
||||
.fl{list-style:none;padding:0;margin:0;}
|
||||
.fl li{display:flex;align-items:flex-start;gap:5px;font-size:11.5px;line-height:1.5;margin-bottom:2px;}
|
||||
.fi{width:13px;height:13px;flex-shrink:0;margin-top:1px;}
|
||||
|
||||
/* ── ACCORDION ── */
|
||||
details.acc>summary{list-style:none;display:flex;justify-content:space-between;align-items:center;padding:9px 16px;cursor:pointer;background:#f9fafb;border-top:1px solid #f1f5f9;font-size:11px;font-weight:700;color:#374151;user-select:none;}
|
||||
details.acc>summary::-webkit-details-marker{display:none;}
|
||||
details.acc>summary .chev{transition:transform .2s;}
|
||||
details.acc[open]>summary .chev{transform:rotate(180deg);}
|
||||
details.acc .ab{padding:12px 16px;display:flex;flex-direction:column;gap:9px;}
|
||||
|
||||
/* ── TRACKING ── */
|
||||
.trk-wrap{display:flex;flex-direction:column;gap:10px;}
|
||||
.trk-step{display:flex;align-items:flex-start;gap:10px;}
|
||||
.trk-point{width:22px;height:22px;border-radius:50%;display:flex;align-items:center;justify-content:center;flex-shrink:0;font-size:11px;font-weight:800;}
|
||||
.trk-line{width:2px;height:18px;margin-left:10px;border-radius:999px;background:#e5e7eb;}
|
||||
|
||||
/* ── MAIN GRID (2-col di desktop, 1-col di tablet/mobile) ── */
|
||||
.mg{display:grid;grid-template-columns:1fr 310px;gap:12px;align-items:start;}
|
||||
@media(max-width:1024px){
|
||||
.mg{grid-template-columns:1fr;}
|
||||
}
|
||||
|
||||
/* ── INNER GRIDS ── */
|
||||
@media(max-width:860px){
|
||||
.g4{grid-template-columns:1fr 1fr;}
|
||||
.g3{grid-template-columns:1fr 1fr;}
|
||||
}
|
||||
/* Di bawah 540px: semua jadi 1 kolom kecuali .g2 tetap 2 */
|
||||
@media(max-width:540px){
|
||||
.g3,.g4{grid-template-columns:1fr;}
|
||||
}
|
||||
@media(max-width:420px){
|
||||
.g2{grid-template-columns:1fr;}
|
||||
}
|
||||
|
||||
/* ── BANNER ── */
|
||||
.bnr-inner{display:flex;align-items:center;gap:14px;flex-wrap:nowrap;}
|
||||
.bnr-mid{flex:1;min-width:0;}
|
||||
.bnr-right{display:flex;align-items:center;gap:10px;flex-shrink:0;}
|
||||
|
||||
@media(max-width:540px){
|
||||
.bnr-inner{flex-wrap:wrap;gap:10px;}
|
||||
.bnr-right{
|
||||
width:100%;
|
||||
display:flex;
|
||||
justify-content:space-between;
|
||||
align-items:center;
|
||||
gap:8px;
|
||||
flex-wrap:wrap;
|
||||
}
|
||||
.bnr-prob-num{font-size:22px!important;}
|
||||
}
|
||||
|
||||
/* ── STATUS ROW ── */
|
||||
.st-row{display:flex;flex-wrap:wrap;align-items:flex-start;gap:12px;}
|
||||
.st-dates{display:flex;gap:18px;flex-wrap:wrap;}
|
||||
|
||||
@media(max-width:480px){
|
||||
.st-dates{flex-direction:column;gap:8px;}
|
||||
}
|
||||
|
||||
/* ── FOTO GRID: max 2 kolom di mobile ── */
|
||||
@media(max-width:540px){
|
||||
.foto-g{grid-template-columns:1fr 1fr!important;}
|
||||
}
|
||||
@media(max-width:360px){
|
||||
.foto-g{grid-template-columns:1fr!important;}
|
||||
}
|
||||
.hero-banner { background: linear-gradient(135deg,#1e40af 0%,#2563eb 60%,#3b82f6 100%); border-radius:1rem; padding:1.5rem; position:relative; overflow:hidden; margin-bottom:1.25rem; }
|
||||
.hero-bubble { position:absolute; border-radius:50%; pointer-events:none; }
|
||||
.detail-card { background:#fff; border-radius:1rem; border:1px solid #f1f5f9; margin-bottom:1rem; overflow:hidden; }
|
||||
.detail-card-head { padding:.65rem 1.1rem; border-bottom:1px solid #f1f5f9; display:flex; align-items:center; gap:8px; background:#f9fafb; }
|
||||
.detail-card-accent{ width:3px; height:16px; border-radius:2px; flex-shrink:0; }
|
||||
.detail-card-title { font-size:11px; font-weight:600; text-transform:uppercase; letter-spacing:.06em; color:#6b7280; }
|
||||
.detail-card-body { padding:1rem 1.1rem; }
|
||||
.data-grid { display:grid; grid-template-columns:repeat(auto-fill,minmax(160px,1fr)); gap:1rem; }
|
||||
.data-lbl { font-size:10px; font-weight:600; text-transform:uppercase; letter-spacing:.05em; color:#9ca3af; margin-bottom:3px; }
|
||||
.data-val { font-size:14px; color:#111827; font-weight:500; }
|
||||
.badge-rt { display:inline-flex; align-items:center; padding:3px 10px; border-radius:20px; font-size:12px; background:#dbeafe; color:#1e40af; font-weight:600; }
|
||||
.donut-ring { transform:rotate(-90deg); transform-origin:36px 36px; }
|
||||
.progress-track{ height:6px; border-radius:3px; background:#e5e7eb; overflow:hidden; }
|
||||
.faktor-item { display:flex; align-items:flex-start; gap:8px; padding:7px 10px; border-radius:8px; font-size:13px; margin-bottom:5px; }
|
||||
.faktor-pos { background:#f0fdf4; color:#166534; }
|
||||
.faktor-neg { background:#fff1f2; color:#9f1239; }
|
||||
.penjelasan-row { display:flex; justify-content:space-between; align-items:center; padding:7px 0; font-size:13px; border-bottom:1px solid #f1f5f9; }
|
||||
.penjelasan-row:last-child { border-bottom:none; }
|
||||
.ringkasan-box { background:#f0f9ff; border:1px solid #bae6fd; border-radius:10px; padding:12px 14px; font-size:13px; color:#0c4a6e; line-height:1.6; }
|
||||
.track-step { display:flex; gap:.875rem; padding:.65rem 0; }
|
||||
.track-num { width:28px; height:28px; border-radius:50%; display:flex; align-items:center; justify-content:center; font-size:12px; font-weight:600; flex-shrink:0; margin-top:2px; }
|
||||
.step-done { background:#dcfce7; color:#15803d; }
|
||||
.step-active { background:#dbeafe; color:#1d4ed8; }
|
||||
.step-pending{ background:#f3f4f6; color:#9ca3af; }
|
||||
.track-line { width:2px; height:16px; background:#e5e7eb; margin:0 auto; }
|
||||
.status-bar { background:#f9fafb; border:1px solid #f1f5f9; border-radius:10px; padding:14px 16px; display:flex; align-items:center; gap:12px; flex-wrap:wrap; }
|
||||
.status-pill{ display:inline-flex; align-items:center; gap:5px; padding:4px 12px; border-radius:20px; font-size:12px; font-weight:600; }
|
||||
.foto-grid { display:grid; grid-template-columns:repeat(4,1fr); gap:8px; }
|
||||
@media(max-width:480px){ .foto-grid{ grid-template-columns:repeat(3,1fr); } }
|
||||
.foto-thumb { aspect-ratio:1; border-radius:8px; overflow:hidden; border:1px solid #e5e7eb; position:relative; cursor:pointer; }
|
||||
.foto-thumb img { width:100%; height:100%; object-fit:cover; display:block; }
|
||||
.foto-placeholder{ width:100%; height:100%; background:#f9fafb; display:flex; flex-direction:column; align-items:center; justify-content:center; gap:4px; }
|
||||
.foto-label{ position:absolute; bottom:0; left:0; right:0; background:rgba(0,0,0,.5); color:#fff; font-size:10px; text-align:center; padding:3px 4px; }
|
||||
.zona-bahaya { border:1px solid #fecaca; border-radius:1rem; overflow:hidden; margin-bottom:1rem; }
|
||||
.zona-bahaya-head { padding:.65rem 1.1rem; border-bottom:1px solid #fecaca; display:flex; align-items:center; gap:8px; background:#fff5f5; }
|
||||
.detail-layout { display:grid; grid-template-columns:1fr 320px; gap:1rem; align-items:start; }
|
||||
@media(max-width:900px){ .detail-layout{ grid-template-columns:1fr; } }
|
||||
.badge-layak { background:#dcfce7; color:#15803d; padding:5px 14px; border-radius:20px; font-size:13px; font-weight:600; display:inline-flex; align-items:center; gap:5px; }
|
||||
.badge-tl { background:#fee2e2; color:#b91c1c; padding:5px 14px; border-radius:20px; font-size:13px; font-weight:600; display:inline-flex; align-items:center; gap:5px; }
|
||||
.badge-draft { display:inline-flex; align-items:center; gap:5px; padding:4px 10px; border-radius:20px; font-size:12px; font-weight:500; background:rgba(255,255,255,.15); color:#fff; border:1px solid rgba(255,255,255,.25); }
|
||||
</style>
|
||||
|
||||
@php
|
||||
$tracking = $calonPenerima->tracking_status ?? 'draft';
|
||||
$st = $calonPenerima->status_verifikasi ?? 'pending';
|
||||
$pred = $calonPenerima->prediksiKelayakan ?? null;
|
||||
$prob = $pred ? (float)$pred->probability : null;
|
||||
$rec = $pred ? ($pred->recommendation ?? '-') : null;
|
||||
$probPct = $prob !== null ? ($prob <= 1 ? $prob * 100 : $prob) : null;
|
||||
$posTop = array_slice($explanation['positive'] ?? [], 0, 3);
|
||||
$negTop = array_slice($explanation['negative'] ?? [], 0, 2);
|
||||
$circum = round(2 * M_PI * 24, 2);
|
||||
$offset = $probPct !== null ? round($circum * (1 - $probPct / 100), 2) : $circum;
|
||||
$sc = $probPct !== null ? ($probPct >= 70 ? '#10b981' : ($probPct >= 40 ? '#f59e0b' : '#f43f5e')) : '#e5e7eb';
|
||||
$recBg = $probPct !== null ? ($probPct >= 70 ? 'background:#f0fdf4;color:#166534;border:1px solid #bbf7d0;' : ($probPct >= 40 ? 'background:#fffbeb;color:#b45309;border:1px solid #fde68a;' : 'background:#fff1f2;color:#9f1239;border:1px solid #fecdd3;')) : '';
|
||||
|
||||
$trackingLabel = match($tracking) {
|
||||
'draft' => 'Draft',
|
||||
'terkirim' => 'Terkirim ke Kelurahan',
|
||||
'sedang_validasi' => 'Sedang Divalidasi',
|
||||
'selesai' => 'Data Sudah Tervalidasi',
|
||||
default => '-'
|
||||
};
|
||||
|
||||
$hasilLabel = match($st) {
|
||||
'disetujui' => 'Diterima',
|
||||
'ditolak' => 'Tidak Diterima',
|
||||
default => 'Menunggu Hasil'
|
||||
};
|
||||
$prediksi = $calonPenerima->prediksiKelayakan;
|
||||
$prob = 0;
|
||||
$label = null;
|
||||
if ($prediksi) {
|
||||
$prob = $prediksi->probability <= 1 ? round($prediksi->probability * 100) : round($prediksi->probability);
|
||||
$label = $prediksi->recommendation; // 'Layak' atau 'Tidak Layak'
|
||||
}
|
||||
$isLayak = $label === 'Layak';
|
||||
$ringColor = $isLayak ? '#22c55e' : ($label ? '#ef4444' : '#d1d5db');
|
||||
$r = 28;
|
||||
$circ = round(2 * M_PI * $r, 2);
|
||||
$fill = round($circ * $prob / 100, 2);
|
||||
$gap = $circ - $fill;
|
||||
$currentStatus = $calonPenerima->tracking_status ?? 'draft';
|
||||
@endphp
|
||||
|
||||
@if(session('success'))
|
||||
<div style="display:flex;align-items:center;gap:9px;background:#f0fdf4;border:1.5px solid #bbf7d0;border-radius:12px;padding:9px 14px;margin-bottom:10px;font-size:12px;color:#166534;font-weight:500;">
|
||||
<svg width="14" height="14" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z"/></svg>
|
||||
{{ session('success') }}
|
||||
</div>
|
||||
@endif
|
||||
@if(session('error'))
|
||||
<div style="display:flex;align-items:center;gap:9px;background:#fff1f2;border:1.5px solid #fecdd3;border-radius:12px;padding:9px 14px;margin-bottom:10px;font-size:12px;color:#9f1239;font-weight:500;">
|
||||
<svg width="14" height="14" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 9v2m0 4h.01M12 3a9 9 0 100 18A9 9 0 0012 3z"/></svg>
|
||||
{{ session('error') }}
|
||||
</div>
|
||||
@endif
|
||||
|
||||
{{-- ══ BANNER ══ --}}
|
||||
<div style="position:relative;overflow:hidden;background:linear-gradient(135deg,#1e40af 0%,#2563eb 55%,#3b82f6 100%);border-radius:18px;padding:16px 20px;margin-bottom:12px;box-shadow:0 6px 20px rgba(37,99,235,.22);">
|
||||
<div style="position:absolute;top:-24px;right:-24px;width:110px;height:110px;background:rgba(255,255,255,.08);border-radius:50%;pointer-events:none;"></div>
|
||||
<div style="position:absolute;bottom:-28px;left:42%;width:80px;height:80px;background:rgba(255,255,255,.06);border-radius:50%;pointer-events:none;"></div>
|
||||
|
||||
<div class="bnr-inner">
|
||||
{{-- Avatar --}}
|
||||
<div style="width:46px;height:46px;border-radius:13px;background:rgba(255,255,255,.18);border:2px solid rgba(255,255,255,.25);display:flex;align-items:center;justify-content:center;font-size:20px;font-weight:900;color:#fff;flex-shrink:0;">
|
||||
{{ strtoupper(substr($calonPenerima->nama_lengkap ?? 'U', 0, 1)) }}
|
||||
</div>
|
||||
|
||||
{{-- Nama + NIK + status --}}
|
||||
<div class="bnr-mid">
|
||||
<h3 style="font-size:15px;font-weight:900;color:#fff;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;line-height:1.2;">{{ $calonPenerima->nama_lengkap ?? '-' }}</h3>
|
||||
<p style="font-size:11px;color:rgba(191,219,254,.85);font-family:monospace;margin-top:2px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;">NIK: {{ $calonPenerima->nik ?? '-' }}</p>
|
||||
<div style="margin-top:6px;">
|
||||
<span style="display:inline-flex;align-items:center;gap:5px;padding:4px 10px;border-radius:999px;background:rgba(255,255,255,.15);color:#fff;font-size:10.5px;font-weight:700;border:1px solid rgba(255,255,255,.22);">
|
||||
<span style="width:6px;height:6px;border-radius:50%;background:#fff;opacity:.9;"></span>
|
||||
{{ $trackingLabel }}
|
||||
</span>
|
||||
{{-- HERO --}}
|
||||
<div class="hero-banner">
|
||||
<div class="hero-bubble" style="width:180px;height:180px;background:rgba(255,255,255,.06);top:-40px;right:-30px;"></div>
|
||||
<div class="hero-bubble" style="width:90px;height:90px;background:rgba(255,255,255,.04);top:50px;right:100px;"></div>
|
||||
<div class="flex items-start justify-between gap-3 flex-wrap">
|
||||
<div class="flex items-center gap-3">
|
||||
<div class="w-12 h-12 rounded-full bg-white/20 flex items-center justify-center text-white font-semibold text-lg flex-shrink-0">
|
||||
{{ strtoupper(substr($calonPenerima->nama_lengkap, 0, 1)) }}
|
||||
</div>
|
||||
<div>
|
||||
<div class="text-white font-semibold text-xl leading-tight">{{ $calonPenerima->nama_lengkap }}</div>
|
||||
<div class="text-white/70 text-sm">NIK: {{ $calonPenerima->nik }}</div>
|
||||
<div class="mt-1.5">
|
||||
<span class="badge-draft">
|
||||
<span class="w-1.5 h-1.5 rounded-full bg-yellow-400 inline-block"></span>
|
||||
{{ ucfirst($currentStatus) }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{-- Probabilitas + pill status --}}
|
||||
<div class="bnr-right">
|
||||
@if($probPct !== null)
|
||||
<div style="text-align:right;">
|
||||
<div class="bnr-prob-num" style="font-size:26px;font-weight:900;color:#fff;line-height:1;">{{ number_format($probPct,0) }}<span style="font-size:13px;color:rgba(191,219,254,.7);">%</span></div>
|
||||
<div style="font-size:10px;color:rgba(191,219,254,.6);margin-top:1px;">probabilitas</div>
|
||||
<div class="text-right">
|
||||
@if($prediksi)
|
||||
<div class="text-white font-semibold leading-none" style="font-size:42px;">
|
||||
{{ $prob }}<span style="font-size:20px;">%</span>
|
||||
</div>
|
||||
<div class="text-white/65 text-xs mt-1">probabilitas</div>
|
||||
<div class="mt-1.5">
|
||||
@if($isLayak)
|
||||
<span class="badge-layak">● Layak</span>
|
||||
@else
|
||||
<span class="badge-tl">● Tidak Layak</span>
|
||||
@endif
|
||||
</div>
|
||||
@endif
|
||||
@if($tracking === 'selesai')
|
||||
@if($st === 'disetujui')
|
||||
<span class="pill" style="background:#f0fdf4;color:#166534;border:1px solid #bbf7d0;"><span class="dot" style="background:#22c55e;"></span>Diterima</span>
|
||||
@elseif($st === 'ditolak')
|
||||
<span class="pill" style="background:#fff1f2;color:#9f1239;border:1px solid #fecdd3;"><span class="dot" style="background:#f43f5e;"></span>Tidak Diterima</span>
|
||||
@else
|
||||
<span class="pill" style="background:#fffbeb;color:#b45309;border:1px solid #fde68a;"><span class="dot" style="background:#f59e0b;"></span>Menunggu Hasil</span>
|
||||
@endif
|
||||
@else
|
||||
<span class="pill" style="background:#eff6ff;color:#1d4ed8;border:1px solid #bfdbfe;"><span class="dot" style="background:#3b82f6;"></span>{{ $trackingLabel }}</span>
|
||||
<div class="text-white/50 text-sm mt-2">Belum diprediksi</div>
|
||||
@endif
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{-- ══ MAIN GRID ══ --}}
|
||||
<div class="mg">
|
||||
{{-- ACTION BUTTONS --}}
|
||||
<div class="flex items-center gap-2 flex-wrap mb-4">
|
||||
@if($currentStatus === 'draft')
|
||||
<a href="{{ route('rt.calon-penerima.edit', $calonPenerima->id) }}"
|
||||
class="inline-flex items-center gap-2 px-4 py-2 text-sm font-medium rounded-xl border border-blue-200 bg-blue-50 text-blue-700 hover:bg-blue-100 transition">
|
||||
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15.232 5.232l3.536 3.536M9 13l6.586-6.586a2 2 0 012.828 2.828L11.828 16H9v-2.828z"/></svg>
|
||||
Edit Data
|
||||
</a>
|
||||
<form action="{{ route('rt.calon-penerima.ajukan', $calonPenerima->id) }}" method="POST" class="inline">
|
||||
@csrf
|
||||
<button type="submit"
|
||||
class="inline-flex items-center gap-2 px-5 py-2 text-sm font-semibold rounded-xl bg-blue-600 text-white hover:bg-blue-700 transition shadow-md shadow-blue-200">
|
||||
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 19l9 2-9-18-9 18 9-2zm0 0v-8"/></svg>
|
||||
Ajukan ke perangkat desa
|
||||
</button>
|
||||
</form>
|
||||
@endif
|
||||
</div>
|
||||
|
||||
{{-- ── KIRI ── --}}
|
||||
<div style="display:flex;flex-direction:column;gap:12px;min-width:0;">
|
||||
{{-- MAIN LAYOUT --}}
|
||||
<div class="detail-layout">
|
||||
|
||||
<div class="g2">
|
||||
{{-- Identitas --}}
|
||||
<div class="sp-card">
|
||||
<div class="sp-head"><div class="bar" style="background:#2563eb;"></div><h3>Identitas</h3></div>
|
||||
<div class="sp-body">
|
||||
<div class="g2">
|
||||
<div><p class="lbl">No. KK</p><p class="val" style="font-family:monospace;font-size:11px;word-break:break-all;">{{ $calonPenerima->no_kk ?? '-' }}</p></div>
|
||||
<div><p class="lbl">Jenis Kelamin</p><p class="val">{{ $calonPenerima->jenis_kelamin ?? '-' }}</p></div>
|
||||
<div><p class="lbl">Tempat Lahir</p><p class="val">{{ $calonPenerima->tempat_lahir ?? '-' }}</p></div>
|
||||
<div><p class="lbl">Tanggal Lahir</p><p class="val">{{ $calonPenerima->tanggal_lahir ? \Carbon\Carbon::parse($calonPenerima->tanggal_lahir)->translatedFormat('d F Y') : '-' }}</p></div>
|
||||
<div><p class="lbl">Usia</p><p class="val">{{ $calonPenerima->usia ?? '-' }} tahun</p></div>
|
||||
<div><p class="lbl">Status Kawin</p><p class="val">{{ $calonPenerima->status_perkawinan ?? '-' }}</p></div>
|
||||
</div>
|
||||
{{-- ═══ KOLOM KIRI ═══ --}}
|
||||
<div>
|
||||
|
||||
{{-- IDENTITAS --}}
|
||||
<div class="detail-card">
|
||||
<div class="detail-card-head">
|
||||
<div class="detail-card-accent" style="background:#2563eb;"></div>
|
||||
<span class="detail-card-title">Identitas</span>
|
||||
</div>
|
||||
<div class="detail-card-body">
|
||||
<div class="data-grid">
|
||||
<div><div class="data-lbl">No. KK</div><div class="data-val">{{ $calonPenerima->no_kk }}</div></div>
|
||||
<div><div class="data-lbl">Jenis Kelamin</div><div class="data-val">{{ $calonPenerima->jenis_kelamin }}</div></div>
|
||||
<div><div class="data-lbl">Tempat Lahir</div><div class="data-val">{{ $calonPenerima->tempat_lahir }}</div></div>
|
||||
<div><div class="data-lbl">Tanggal Lahir</div><div class="data-val">{{ \Carbon\Carbon::parse($calonPenerima->tanggal_lahir)->translatedFormat('d F Y') }}</div></div>
|
||||
<div><div class="data-lbl">Usia</div><div class="data-val">{{ $calonPenerima->usia }} tahun</div></div>
|
||||
<div><div class="data-lbl">Status Kawin</div><div class="data-val">{{ $calonPenerima->status_perkawinan }}</div></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{-- Tempat Tinggal --}}
|
||||
<div class="sp-card">
|
||||
<div class="sp-head"><div class="bar" style="background:#10b981;"></div><h3>Tempat Tinggal</h3></div>
|
||||
<div class="sp-body" style="display:flex;flex-direction:column;gap:11px;">
|
||||
<div><p class="lbl">Alamat</p><p class="val">{{ $calonPenerima->alamat ?? '-' }}</p></div>
|
||||
<div class="g2">
|
||||
<div><p class="lbl">Dusun</p><p class="val">{{ $calonPenerima->desa ?? '-' }}</p></div>
|
||||
<div>
|
||||
<p class="lbl">RT</p>
|
||||
<span style="display:inline-flex;align-items:center;padding:2px 9px;border-radius:8px;background:#eff6ff;color:#1d4ed8;font-size:11px;font-weight:700;border:1px solid #bfdbfe;">RT {{ str_pad($calonPenerima->rt->nomor_rt ?? 0,3,'0',STR_PAD_LEFT) }}</span>
|
||||
{{-- TEMPAT TINGGAL --}}
|
||||
<div class="detail-card">
|
||||
<div class="detail-card-head">
|
||||
<div class="detail-card-accent" style="background:#10b981;"></div>
|
||||
<span class="detail-card-title">Tempat Tinggal</span>
|
||||
</div>
|
||||
<div class="detail-card-body">
|
||||
<div class="data-grid">
|
||||
<div style="grid-column:span 2;">
|
||||
<div class="data-lbl">Alamat</div>
|
||||
<div class="data-val">{{ $calonPenerima->alamat }}</div>
|
||||
</div>
|
||||
<div><div class="data-lbl">Dusun</div><div class="data-val">{{ $calonPenerima->desa }}</div></div>
|
||||
<div>
|
||||
<div class="data-lbl">RT</div>
|
||||
<div class="data-val"><span class="badge-rt">RT {{ str_pad($calonPenerima->rt->nomor_rt ?? '?', 3, '0', STR_PAD_LEFT) }}</span></div>
|
||||
</div>
|
||||
<div><div class="data-lbl">Aset Kepemilikan</div><div class="data-val">{{ $calonPenerima->aset_kepemilikan ?? '-' }}</div></div>
|
||||
<div><div class="data-lbl">Lantai</div><div class="data-val">{{ $calonPenerima->lantai_rumah }}</div></div>
|
||||
<div><div class="data-lbl">Dinding</div><div class="data-val">{{ $calonPenerima->dinding_rumah }}</div></div>
|
||||
<div><div class="data-lbl">Atap</div><div class="data-val">{{ $calonPenerima->atap_rumah }}</div></div>
|
||||
<div><div class="data-lbl">Luas Rumah</div><div class="data-val">{{ $calonPenerima->luas_rumah_m2 }} m²</div></div>
|
||||
<div><div class="data-lbl">Status Kepemilikan</div><div class="data-val">{{ $calonPenerima->status_kepemilikan_rumah }}</div></div>
|
||||
<div><div class="data-lbl">Meteran Listrik</div><div class="data-val">{{ $calonPenerima->meteran_listrik }}</div></div>
|
||||
<div><div class="data-lbl">Sumber Air</div><div class="data-val">{{ $calonPenerima->sumber_air }}</div></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{-- DATA EKONOMI --}}
|
||||
<div class="detail-card">
|
||||
<div class="detail-card-head">
|
||||
<div class="detail-card-accent" style="background:#f59e0b;"></div>
|
||||
<span class="detail-card-title">Data Ekonomi</span>
|
||||
</div>
|
||||
<div class="detail-card-body">
|
||||
<div class="data-grid">
|
||||
<div><div class="data-lbl">Pekerjaan</div><div class="data-val">{{ $calonPenerima->pekerjaan }}</div></div>
|
||||
<div><div class="data-lbl">Penghasilan</div><div class="data-val">Rp {{ number_format($calonPenerima->penghasilan, 0, ',', '.') }}</div></div>
|
||||
<div><div class="data-lbl">Tanggungan</div><div class="data-val">{{ $calonPenerima->jumlah_tanggungan }} orang</div></div>
|
||||
<div>
|
||||
<div class="data-lbl">Bantuan Lain</div>
|
||||
<div class="data-val">
|
||||
@if($calonPenerima->bantuan_lain === 'ya')
|
||||
<span class="inline-flex items-center px-2 py-0.5 rounded-full text-xs font-medium bg-amber-100 text-amber-700">Ya</span>
|
||||
@else
|
||||
<span class="inline-flex items-center px-2 py-0.5 rounded-full text-xs font-medium bg-green-100 text-green-700">Tidak</span>
|
||||
@endif
|
||||
</div>
|
||||
</div>
|
||||
<div><p class="lbl">Aset Kepemilikan</p><p class="val">{{ $calonPenerima->aset_kepemilikan ?? '-' }}</p></div>
|
||||
<div class="g3">
|
||||
<div><p class="lbl">Kondisi Rumah</p><p class="val">{{ $calonPenerima->kondisi_rumah ?? '-' }}</p></div>
|
||||
<div><p class="lbl">Meteran Listrik</p><p class="val">{{ $calonPenerima->meteran_listrik ?? '-' }}</p></div>
|
||||
<div><p class="lbl">Sumber Air</p><p class="val">{{ $calonPenerima->sumber_air ?? '-' }}</p></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{-- Ekonomi --}}
|
||||
<div class="sp-card">
|
||||
<div class="sp-head"><div class="bar" style="background:#f59e0b;"></div><h3>Data Ekonomi</h3></div>
|
||||
<div class="sp-body">
|
||||
<div class="g4">
|
||||
<div><p class="lbl">Pekerjaan</p><p class="val">{{ $calonPenerima->pekerjaan ?? '-' }}</p></div>
|
||||
<div><p class="lbl">Penghasilan</p><p class="val">Rp {{ number_format((float)($calonPenerima->penghasilan??0),0,',','.') }}</p></div>
|
||||
<div><p class="lbl">Tanggungan</p><p class="val">{{ $calonPenerima->jumlah_tanggungan ?? '-' }} orang</p></div>
|
||||
<div>
|
||||
<p class="lbl">Bantuan Lain</p>
|
||||
@if(($calonPenerima->bantuan_lain??'') === 'ya')
|
||||
<span style="display:inline-flex;padding:2px 9px;border-radius:20px;background:#fffbeb;color:#b45309;font-size:11px;font-weight:700;">Ya</span>
|
||||
@else
|
||||
<span style="display:inline-flex;padding:2px 9px;border-radius:20px;background:#f3f4f6;color:#6b7280;font-size:11px;font-weight:700;">Tidak</span>
|
||||
@endif
|
||||
</div>
|
||||
</div>
|
||||
{{-- FOTO & DOKUMEN --}}
|
||||
<div class="detail-card">
|
||||
<div class="detail-card-head">
|
||||
<div class="detail-card-accent" style="background:#e11d48;"></div>
|
||||
<span class="detail-card-title">Foto & Dokumen</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{-- Dokumen & Foto --}}
|
||||
@php
|
||||
$fotoFields = [
|
||||
'foto_rumah_depan' => 'Foto Rumah Depan',
|
||||
'foto_rumah_belakang' => 'Foto Rumah Belakang',
|
||||
'foto_rumah_kanan' => 'Foto Rumah Kanan',
|
||||
'foto_rumah_kiri' => 'Foto Rumah Kiri',
|
||||
'foto_kk' => 'Foto KK',
|
||||
'foto_ktp' => 'Foto KTP',
|
||||
'foto_rekening_listrik' => 'Rekening Listrik',
|
||||
'foto_meteran_air' => 'Foto Meteran Air',
|
||||
'dokumen_pendukung' => 'Dokumen Pendukung',
|
||||
];
|
||||
$adaFoto = false;
|
||||
foreach($fotoFields as $field => $label) {
|
||||
if($calonPenerima->$field) { $adaFoto = true; break; }
|
||||
}
|
||||
@endphp
|
||||
@if($adaFoto)
|
||||
<div class="sp-card">
|
||||
<div class="sp-head"><div class="bar" style="background:#e11d48;"></div><h3>Dokumen & Foto</h3></div>
|
||||
<div class="sp-body">
|
||||
<div class="foto-g" style="display:grid;grid-template-columns:repeat(4,1fr);gap:10px;">
|
||||
@foreach($fotoFields as $field => $label)
|
||||
<div class="detail-card-body">
|
||||
@php
|
||||
$fotoFields = [
|
||||
'foto_rumah_depan' => 'Rumah Depan',
|
||||
'foto_rumah_belakang' => 'Rumah Belakang',
|
||||
'foto_rumah_kanan' => 'Rumah Kanan',
|
||||
'foto_rumah_kiri' => 'Rumah Kiri',
|
||||
'foto_kk' => 'Foto KK',
|
||||
'foto_ktp' => 'Foto KTP',
|
||||
'foto_rekening_listrik' => 'Rekening Listrik',
|
||||
'foto_meteran_air' => 'Meteran Air',
|
||||
];
|
||||
@endphp
|
||||
<div class="foto-grid">
|
||||
@foreach($fotoFields as $field => $label)
|
||||
<div class="foto-thumb" @if($calonPenerima->$field) onclick="openFoto('{{ asset('storage/' . $calonPenerima->$field) }}')" @endif>
|
||||
@if($calonPenerima->$field)
|
||||
<div>
|
||||
<p class="lbl" style="margin-bottom:5px;">{{ $label }}</p>
|
||||
@php $ext = pathinfo($calonPenerima->$field, PATHINFO_EXTENSION); @endphp
|
||||
@if(in_array(strtolower($ext), ['jpg','jpeg','png','gif','webp']))
|
||||
<a href="{{ Storage::url($calonPenerima->$field) }}" target="_blank">
|
||||
<img src="{{ Storage::url($calonPenerima->$field) }}"
|
||||
alt="{{ $label }}"
|
||||
style="width:100%;height:80px;object-fit:cover;border-radius:10px;border:1.5px solid #e5e7eb;">
|
||||
</a>
|
||||
@else
|
||||
<a href="{{ Storage::url($calonPenerima->$field) }}" target="_blank"
|
||||
style="display:inline-flex;align-items:center;gap:5px;padding:5px 10px;background:#eff6ff;color:#2563eb;font-size:11px;font-weight:700;border-radius:8px;text-decoration:none;border:1px solid #bfdbfe;">
|
||||
<svg width="12" height="12" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 10v6m0 0l-3-3m3 3l3-3m2 8H7a2 2 0 01-2-2V5a2 2 0 012-2h5l2 2h5a2 2 0 012 2v7"/></svg>
|
||||
Lihat File
|
||||
</a>
|
||||
@endif
|
||||
<img src="{{ asset('storage/' . $calonPenerima->$field) }}" alt="{{ $label }}" loading="lazy">
|
||||
@else
|
||||
<div class="foto-placeholder">
|
||||
<svg class="w-6 h-6 text-gray-300" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" d="M4 16l4.586-4.586a2 2 0 012.828 0L16 16m-2-2l1.586-1.586a2 2 0 012.828 0L20 14m-6-6h.01M6 20h12a2 2 0 002-2V6a2 2 0 00-2-2H6a2 2 0 00-2 2v12a2 2 0 002 2z"/>
|
||||
</svg>
|
||||
<span class="text-xs text-gray-400">Belum ada</span>
|
||||
</div>
|
||||
@endif
|
||||
<div class="foto-label">{{ $label }}</div>
|
||||
</div>
|
||||
@endforeach
|
||||
</div>
|
||||
@if($calonPenerima->dokumen_pendukung)
|
||||
<div class="mt-3 pt-3 border-t border-gray-100">
|
||||
<a href="{{ asset('storage/' . $calonPenerima->dokumen_pendukung) }}" target="_blank"
|
||||
class="inline-flex items-center gap-2 text-sm text-blue-600 hover:underline">
|
||||
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15.172 7l-6.586 6.586a2 2 0 102.828 2.828l6.414-6.586a4 4 0 00-5.656-5.656l-6.415 6.585a6 6 0 108.486 8.486L20.5 13"/></svg>
|
||||
Lihat Dokumen Pendukung
|
||||
</a>
|
||||
</div>
|
||||
@endif
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{-- TRACKING PENGAJUAN --}}
|
||||
<div class="detail-card">
|
||||
<div class="detail-card-head">
|
||||
<div class="detail-card-accent" style="background:#8b5cf6;"></div>
|
||||
<span class="detail-card-title">Tracking Pengajuan</span>
|
||||
</div>
|
||||
<div class="detail-card-body" style="padding-top:.5rem;padding-bottom:.5rem;">
|
||||
@php
|
||||
$steps = [
|
||||
'draft' => ['label' => 'Data dibuat RT', 'desc' => 'Data masih bisa diubah dan dihapus sebelum diajukan.'],
|
||||
'terkirim' => ['label' => 'Data diajukan ke kelurahan', 'desc' => 'Menunggu tindakan RT untuk mengajukan data.'],
|
||||
'validasi' => ['label' => 'Proses validasi & filterisasi','desc' => 'Menunggu admin memulai proses validasi.'],
|
||||
'final' => ['label' => 'Hasil akhir ditetapkan', 'desc' => 'Menunggu hasil akhir dari admin perangkat desa.'],
|
||||
];
|
||||
$statusOrder = array_keys($steps);
|
||||
$currentIndex = array_search($currentStatus, $statusOrder) ?? 0;
|
||||
@endphp
|
||||
@foreach($steps as $key => $step)
|
||||
@php
|
||||
$stepIndex = array_search($key, $statusOrder);
|
||||
$isDone = $stepIndex < $currentIndex;
|
||||
$isActive = $stepIndex === $currentIndex;
|
||||
@endphp
|
||||
<div class="track-step">
|
||||
<div class="flex flex-col items-center">
|
||||
<div class="track-num {{ $isDone ? 'step-done' : ($isActive ? 'step-active' : 'step-pending') }}">
|
||||
@if($isDone)
|
||||
<svg class="w-3.5 h-3.5" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2.5" d="M5 13l4 4L19 7"/></svg>
|
||||
@else
|
||||
{{ $stepIndex + 1 }}
|
||||
@endif
|
||||
</div>
|
||||
@if(!$loop->last)<div class="track-line"></div>@endif
|
||||
</div>
|
||||
<div class="pb-1">
|
||||
<div class="text-sm font-semibold {{ $isActive ? 'text-gray-800' : ($isDone ? 'text-gray-700' : 'text-gray-400') }} leading-snug">
|
||||
{{ $step['label'] }}
|
||||
</div>
|
||||
<div class="text-xs text-gray-400 mt-0.5">{{ $step['desc'] }}</div>
|
||||
</div>
|
||||
</div>
|
||||
@endforeach
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{-- STATUS PENGAJUAN --}}
|
||||
<div class="detail-card">
|
||||
<div class="detail-card-head">
|
||||
<div class="detail-card-accent" style="background:#64748b;"></div>
|
||||
<span class="detail-card-title">Status Pengajuan</span>
|
||||
</div>
|
||||
<div class="detail-card-body">
|
||||
<div class="status-bar">
|
||||
@php
|
||||
$statusMap = [
|
||||
'draft' => ['bg' => 'bg-yellow-100', 'text' => 'text-yellow-700', 'dot' => 'bg-yellow-400', 'label' => 'Draft'],
|
||||
'terkirim' => ['bg' => 'bg-blue-100', 'text' => 'text-blue-700', 'dot' => 'bg-blue-400', 'label' => 'Diajukan'],
|
||||
'validasi' => ['bg' => 'bg-purple-100', 'text' => 'text-purple-700', 'dot' => 'bg-purple-400', 'label' => 'Validasi'],
|
||||
'final' => ['bg' => 'bg-green-100', 'text' => 'text-green-700', 'dot' => 'bg-green-400', 'label' => 'Final'],
|
||||
];
|
||||
$s = $statusMap[$currentStatus] ?? $statusMap['draft'];
|
||||
@endphp
|
||||
<span class="status-pill {{ $s['bg'] }} {{ $s['text'] }}">
|
||||
<span class="w-1.5 h-1.5 rounded-full {{ $s['dot'] }} inline-block"></span>
|
||||
{{ $s['label'] }}
|
||||
</span>
|
||||
<div class="flex gap-6 text-sm text-gray-500 flex-wrap">
|
||||
<div>
|
||||
<span class="text-xs text-gray-400 block">Dibuat</span>
|
||||
<span class="font-medium text-gray-700">{{ $calonPenerima->created_at->translatedFormat('d M Y, H:i') }}</span>
|
||||
</div>
|
||||
<div>
|
||||
<span class="text-xs text-gray-400 block">Diperbarui</span>
|
||||
<span class="font-medium text-gray-700">{{ $calonPenerima->updated_at->translatedFormat('d M Y, H:i') }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>{{-- end kolom kiri --}}
|
||||
|
||||
{{-- ═══ KOLOM KANAN: PREDIKSI ═══ --}}
|
||||
<div>
|
||||
<div style="position:sticky;top:1rem;">
|
||||
|
||||
<div class="detail-card">
|
||||
<div class="detail-card-head">
|
||||
<div class="detail-card-accent" style="background:#22c55e;"></div>
|
||||
<span class="detail-card-title">Prediksi Kelayakan</span>
|
||||
</div>
|
||||
<div class="detail-card-body">
|
||||
|
||||
{{-- Donut --}}
|
||||
<div class="flex items-center gap-4 mb-4">
|
||||
<svg width="80" height="80" viewBox="0 0 72 72">
|
||||
<circle cx="36" cy="36" r="{{ $r }}" fill="none" stroke="#e5e7eb" stroke-width="8"/>
|
||||
<circle cx="36" cy="36" r="{{ $r }}" fill="none"
|
||||
stroke="{{ $ringColor }}" stroke-width="8"
|
||||
stroke-dasharray="{{ $fill }} {{ $gap }}"
|
||||
stroke-linecap="round"
|
||||
class="donut-ring"/>
|
||||
<text x="36" y="40" text-anchor="middle" font-size="13" font-weight="600" fill="#111827">{{ $prob }}%</text>
|
||||
</svg>
|
||||
<div>
|
||||
<div class="text-xs text-gray-400 mb-1.5">Rekomendasi</div>
|
||||
@if($prediksi)
|
||||
@if($isLayak)
|
||||
<span class="badge-layak">
|
||||
<svg class="w-3.5 h-3.5" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2.5" d="M5 13l4 4L19 7"/></svg>
|
||||
Layak
|
||||
</span>
|
||||
@else
|
||||
<span class="badge-tl">
|
||||
<svg class="w-3.5 h-3.5" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2.5" d="M6 18L18 6M6 6l12 12"/></svg>
|
||||
Tidak Layak
|
||||
</span>
|
||||
@endif
|
||||
@else
|
||||
<span class="text-sm text-gray-400">Belum diprediksi</span>
|
||||
@endif
|
||||
<div class="progress-track mt-2" style="width:140px;">
|
||||
<div style="height:100%;border-radius:3px;background:{{ $ringColor }};width:{{ $prob }}%;"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{-- Faktor Pendukung --}}
|
||||
@if(!empty($explanation['positive']))
|
||||
<div class="mb-3">
|
||||
<div class="text-xs font-semibold uppercase tracking-wider text-green-700 mb-2 flex items-center gap-1.5">
|
||||
<svg class="w-3.5 h-3.5" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2.5" d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z"/></svg>
|
||||
Faktor Pendukung
|
||||
</div>
|
||||
@foreach($explanation['positive'] as $f)
|
||||
<div class="faktor-item faktor-pos">
|
||||
<svg class="w-3.5 h-3.5 mt-0.5 flex-shrink-0" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2.5" d="M5 13l4 4L19 7"/></svg>
|
||||
{{ $f }}
|
||||
</div>
|
||||
@endforeach
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@endif
|
||||
|
||||
{{-- Tracking --}}
|
||||
<div class="sp-card">
|
||||
<div class="sp-head"><div class="bar" style="background:#6b7280;"></div><h3>Tracking Pengajuan</h3></div>
|
||||
<div class="sp-body">
|
||||
<div class="trk-wrap">
|
||||
<div class="trk-step">
|
||||
<div class="trk-point" style="{{ in_array($tracking, ['draft','terkirim','sedang_validasi','selesai']) ? 'background:#dbeafe;color:#1d4ed8;' : 'background:#f3f4f6;color:#9ca3af;' }}">1</div>
|
||||
<div>
|
||||
<div style="font-size:12px;font-weight:700;color:#111827;">Data dibuat RT</div>
|
||||
<div style="font-size:11px;color:#9ca3af;">Data masih bisa diubah dan dihapus sebelum diajukan.</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="trk-line"></div>
|
||||
<div class="trk-step">
|
||||
<div class="trk-point" style="{{ in_array($tracking, ['terkirim','sedang_validasi','selesai']) ? 'background:#dbeafe;color:#1d4ed8;' : 'background:#f3f4f6;color:#9ca3af;' }}">2</div>
|
||||
<div>
|
||||
<div style="font-size:12px;font-weight:700;color:#111827;">Data diajukan ke kelurahan</div>
|
||||
<div style="font-size:11px;color:#9ca3af;">
|
||||
@if(in_array($tracking, ['terkirim','sedang_validasi','selesai']))
|
||||
Data sudah dikirim dan tidak bisa diubah lagi.
|
||||
@else
|
||||
Menunggu tindakan RT untuk mengajukan data.
|
||||
@endif
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="trk-line"></div>
|
||||
<div class="trk-step">
|
||||
<div class="trk-point" style="{{ in_array($tracking, ['sedang_validasi','selesai']) ? 'background:#fef3c7;color:#b45309;' : 'background:#f3f4f6;color:#9ca3af;' }}">3</div>
|
||||
<div>
|
||||
<div style="font-size:12px;font-weight:700;color:#111827;">Proses validasi & filterisasi</div>
|
||||
<div style="font-size:11px;color:#9ca3af;">
|
||||
@if(in_array($tracking, ['sedang_validasi','selesai']))
|
||||
Admin sedang atau sudah memproses hasil bantuan berdasarkan kuota dan probabilitas.
|
||||
@else
|
||||
Menunggu admin memulai proses validasi.
|
||||
@endif
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="trk-line"></div>
|
||||
<div class="trk-step">
|
||||
<div class="trk-point" style="{{ $tracking === 'selesai' ? 'background:#dcfce7;color:#166534;' : 'background:#f3f4f6;color:#9ca3af;' }}">4</div>
|
||||
<div>
|
||||
<div style="font-size:12px;font-weight:700;color:#111827;">Hasil akhir ditetapkan</div>
|
||||
<div style="font-size:11px;color:#9ca3af;">
|
||||
@if($tracking === 'selesai')
|
||||
Hasil akhir bantuan telah ditetapkan: <strong style="color:#374151;">{{ $hasilLabel }}</strong>.
|
||||
@else
|
||||
Menunggu hasil akhir dari admin kelurahan.
|
||||
@endif
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{-- Status Pengajuan --}}
|
||||
<div class="sp-card">
|
||||
<div class="sp-head"><div class="bar" style="background:#6b7280;"></div><h3>Status Pengajuan</h3></div>
|
||||
<div class="sp-body">
|
||||
<div class="st-row">
|
||||
@if($tracking === 'draft')
|
||||
<span class="pill" style="background:#f3f4f6;color:#6b7280;border:1px solid #e5e7eb;"><span class="dot" style="background:#9ca3af;"></span>Draft</span>
|
||||
@elseif($tracking === 'terkirim')
|
||||
<span class="pill" style="background:#eff6ff;color:#1d4ed8;border:1px solid #bfdbfe;"><span class="dot" style="background:#3b82f6;"></span>Terkirim ke Kelurahan</span>
|
||||
@elseif($tracking === 'sedang_validasi')
|
||||
<span class="pill" style="background:#fffbeb;color:#b45309;border:1px solid #fde68a;"><span class="dot" style="background:#f59e0b;"></span>Sedang Divalidasi</span>
|
||||
@elseif($tracking === 'selesai')
|
||||
@if($st === 'disetujui')
|
||||
<span class="pill" style="background:#f0fdf4;color:#166534;border:1px solid #bbf7d0;"><span class="dot" style="background:#22c55e;"></span>Diterima</span>
|
||||
@elseif($st === 'ditolak')
|
||||
<span class="pill" style="background:#fff1f2;color:#9f1239;border:1px solid #fecdd3;"><span class="dot" style="background:#f43f5e;"></span>Tidak Diterima</span>
|
||||
@else
|
||||
<span class="pill" style="background:#fffbeb;color:#b45309;border:1px solid #fde68a;"><span class="dot" style="background:#f59e0b;"></span>Menunggu Hasil</span>
|
||||
@endif
|
||||
@endif
|
||||
|
||||
<div class="st-dates">
|
||||
<div><p class="lbl">Dibuat</p><p class="val">{{ $calonPenerima->created_at ? \Carbon\Carbon::parse($calonPenerima->created_at)->translatedFormat('d M Y, H:i') : '-' }}</p></div>
|
||||
<div><p class="lbl">Diperbarui</p><p class="val">{{ $calonPenerima->updated_at ? \Carbon\Carbon::parse($calonPenerima->updated_at)->translatedFormat('d M Y, H:i') : '-' }}</p></div>
|
||||
{{-- Faktor Pengurang --}}
|
||||
@if(!empty($explanation['negative']))
|
||||
<div class="mb-3">
|
||||
<div class="text-xs font-semibold uppercase tracking-wider text-red-700 mb-2 flex items-center gap-1.5">
|
||||
<svg class="w-3.5 h-3.5" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2.5" d="M10 14l2-2m0 0l2-2m-2 2l-2-2m2 2l2 2m7-2a9 9 0 11-18 0 9 9 0 0118 0z"/></svg>
|
||||
Faktor Pengurang
|
||||
</div>
|
||||
@foreach($explanation['negative'] as $f)
|
||||
<div class="faktor-item faktor-neg">
|
||||
<svg class="w-3.5 h-3.5 mt-0.5 flex-shrink-0" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2.5" d="M6 18L18 6M6 6l12 12"/></svg>
|
||||
{{ $f }}
|
||||
</div>
|
||||
@endforeach
|
||||
</div>
|
||||
|
||||
@if(!empty($calonPenerima->catatan_admin))
|
||||
<div style="flex:1;min-width:0;width:100%;background:#f9fafb;border:1px solid #e5e7eb;border-radius:10px;padding:7px 11px;font-size:11.5px;color:#6b7280;">
|
||||
<strong style="color:#374151;">Catatan: </strong>{{ $calonPenerima->catatan_admin }}
|
||||
</div>
|
||||
@endif
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>{{-- end kiri --}}
|
||||
|
||||
{{-- ── KANAN (sidebar) ── --}}
|
||||
<div style="display:flex;flex-direction:column;gap:12px;min-width:0;">
|
||||
|
||||
{{-- Prediksi --}}
|
||||
<div class="sp-card">
|
||||
<div class="sp-head"><div class="bar" style="background:#2563eb;"></div><h3>Prediksi Kelayakan</h3></div>
|
||||
@if($pred)
|
||||
<div class="sp-body" style="display:flex;flex-direction:column;gap:11px;">
|
||||
<div style="display:flex;align-items:center;gap:12px;">
|
||||
<div style="position:relative;width:60px;height:60px;flex-shrink:0;">
|
||||
<svg width="60" height="60" viewBox="0 0 56 56" style="transform:rotate(-90deg);">
|
||||
<circle cx="28" cy="28" r="24" fill="none" stroke="#f1f5f9" stroke-width="6"/>
|
||||
<circle cx="28" cy="28" r="24" fill="none" stroke="{{ $sc }}" stroke-width="6" stroke-dasharray="{{ $circum }}" stroke-dashoffset="{{ $offset }}" stroke-linecap="round"/>
|
||||
</svg>
|
||||
<div style="position:absolute;inset:0;display:flex;align-items:center;justify-content:center;font-size:13px;font-weight:900;color:#111827;">
|
||||
{{ number_format($probPct,0) }}<span style="font-size:9px;color:#9ca3af;">%</span>
|
||||
</div>
|
||||
</div>
|
||||
<div style="flex:1;min-width:0;">
|
||||
<p class="lbl" style="margin-bottom:4px;">Rekomendasi</p>
|
||||
<span style="{{ $recBg }}display:inline-block;padding:3px 10px;border-radius:8px;font-size:11.5px;font-weight:700;">{{ $rec }}</span>
|
||||
<div style="margin-top:7px;height:4px;background:#f1f5f9;border-radius:99px;overflow:hidden;">
|
||||
<div style="height:100%;border-radius:99px;background:{{ $sc }};width:{{ min($probPct,100) }}%;"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@if(!empty($posTop))
|
||||
<div style="background:#f0fdf4;border:1.5px solid #bbf7d0;border-radius:12px;padding:9px 12px;">
|
||||
<p style="font-size:10px;font-weight:700;color:#166534;text-transform:uppercase;letter-spacing:.07em;margin-bottom:5px;">Faktor Pendukung</p>
|
||||
<ul class="fl">@foreach($posTop as $item)<li><svg class="fi" fill="none" stroke="#22c55e" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2.5" d="M5 13l4 4L19 7"/></svg><span style="color:#166534;">{{ $item }}</span></li>@endforeach</ul>
|
||||
</div>
|
||||
@endif
|
||||
|
||||
@if(!empty($negTop))
|
||||
<div style="background:#fff1f2;border:1.5px solid #fecdd3;border-radius:12px;padding:9px 12px;">
|
||||
<p style="font-size:10px;font-weight:700;color:#9f1239;text-transform:uppercase;letter-spacing:.07em;margin-bottom:5px;">Faktor Pengurang</p>
|
||||
<ul class="fl">@foreach($negTop as $item)<li><svg class="fi" fill="none" stroke="#f43f5e" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2.5" d="M6 18L18 6M6 6l12 12"/></svg><span style="color:#9f1239;">{{ $item }}</span></li>@endforeach</ul>
|
||||
</div>
|
||||
@endif
|
||||
|
||||
<details class="acc">
|
||||
<summary>Penjelasan Lengkap <svg class="chev" width="13" height="13" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7"/></svg></summary>
|
||||
<div class="ab">
|
||||
<div style="display:flex;flex-direction:column;gap:5px;">
|
||||
@foreach([
|
||||
'Pekerjaan' => $calonPenerima->pekerjaan??'-',
|
||||
'Penghasilan' => 'Rp '.number_format((float)($calonPenerima->penghasilan??0),0,',','.'),
|
||||
'Tanggungan' => ($calonPenerima->jumlah_tanggungan??'-').' orang',
|
||||
'Aset' => $calonPenerima->aset_kepemilikan??'-',
|
||||
'Bantuan Lain' => ucfirst($calonPenerima->bantuan_lain??'-'),
|
||||
'Usia' => ($calonPenerima->usia??'-').' tahun',
|
||||
'Kondisi Rumah' => $calonPenerima->kondisi_rumah??'-',
|
||||
'Meteran Listrik'=> $calonPenerima->meteran_listrik??'-',
|
||||
'Sumber Air' => $calonPenerima->sumber_air??'-',
|
||||
] as $lbl=>$val)
|
||||
<div style="display:flex;justify-content:space-between;gap:8px;font-size:11px;"><span style="color:#9ca3af;">{{ $lbl }}</span><span style="font-weight:600;color:#374151;text-align:right;word-break:break-word;">{{ $val }}</span></div>
|
||||
@endforeach
|
||||
</div>
|
||||
@if(!empty($explanation['positive']))
|
||||
<div style="background:#f0fdf4;border:1px solid #bbf7d0;border-radius:10px;padding:8px 11px;">
|
||||
<p style="font-size:10px;font-weight:700;color:#166534;text-transform:uppercase;letter-spacing:.06em;margin-bottom:4px;">Semua Pendukung</p>
|
||||
<ul class="fl">@foreach($explanation['positive'] as $i)<li style="font-size:11px;color:#166534;"><span>•</span>{{ $i }}</li>@endforeach</ul>
|
||||
{{-- Penjelasan Lengkap — selalu terbuka --}}
|
||||
<div class="mb-3">
|
||||
<div class="text-xs font-semibold uppercase tracking-wider text-gray-500 mb-2">Penjelasan Lengkap</div>
|
||||
<div class="border border-gray-100 rounded-xl overflow-hidden">
|
||||
@php
|
||||
$penjelasanRows = [
|
||||
'Pekerjaan' => $calonPenerima->pekerjaan,
|
||||
'Penghasilan' => 'Rp ' . number_format($calonPenerima->penghasilan, 0, ',', '.'),
|
||||
'Tanggungan' => $calonPenerima->jumlah_tanggungan . ' orang',
|
||||
'Aset' => $calonPenerima->aset_kepemilikan ?? '-',
|
||||
'Bantuan Lain' => ucfirst($calonPenerima->bantuan_lain),
|
||||
'Usia' => $calonPenerima->usia . ' tahun',
|
||||
'Kondisi Rumah' => ($calonPenerima->kondisi_rumah ?? '-'),
|
||||
'Meteran Listrik' => $calonPenerima->meteran_listrik,
|
||||
'Sumber Air' => $calonPenerima->sumber_air,
|
||||
];
|
||||
@endphp
|
||||
@foreach($penjelasanRows as $k => $v)
|
||||
<div class="penjelasan-row px-3">
|
||||
<span class="text-gray-400">{{ $k }}</span>
|
||||
<span class="text-gray-800 font-medium text-right ml-2">{{ $v }}</span>
|
||||
</div>
|
||||
@endif
|
||||
@if(!empty($explanation['negative']))
|
||||
<div style="background:#fff1f2;border:1px solid #fecdd3;border-radius:10px;padding:8px 11px;">
|
||||
<p style="font-size:10px;font-weight:700;color:#9f1239;text-transform:uppercase;letter-spacing:.06em;margin-bottom:4px;">Semua Pengurang</p>
|
||||
<ul class="fl">@foreach($explanation['negative'] as $i)<li style="font-size:11px;color:#9f1239;"><span>•</span>{{ $i }}</li>@endforeach</ul>
|
||||
</div>
|
||||
@endif
|
||||
@if(!empty($explanation['summary']))
|
||||
<div style="background:#eff6ff;border:1px solid #bfdbfe;border-radius:10px;padding:8px 11px;">
|
||||
<p style="font-size:10px;font-weight:700;color:#1e40af;text-transform:uppercase;letter-spacing:.06em;margin-bottom:3px;">Ringkasan</p>
|
||||
<p style="font-size:11.5px;color:#1e3a8a;line-height:1.6;">{{ $explanation['summary'] }}</p>
|
||||
</div>
|
||||
@endif
|
||||
@endforeach
|
||||
</div>
|
||||
</details>
|
||||
</div>
|
||||
@else
|
||||
<div style="padding:28px 16px;text-align:center;">
|
||||
<div style="width:38px;height:38px;background:#f3f4f6;border-radius:50%;display:flex;align-items:center;justify-content:center;margin:0 auto 7px;">
|
||||
<svg width="17" height="17" fill="none" stroke="#d1d5db" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 19v-6a2 2 0 00-2-2H5a2 2 0 00-2 2v6a2 2 0 002 2h2a2 2 0 002-2zm0 0V9a2 2 0 012-2h2a2 2 0 012 2v10m-6 0a2 2 0 002 2h2a2 2 0 002-2m0 0V5a2 2 0 012-2h2a2 2 0 012 2v14a2 2 0 01-2 2h-2a2 2 0 01-2-2z"/></svg>
|
||||
</div>
|
||||
<p style="font-size:11px;color:#9ca3af;">Belum ada data prediksi</p>
|
||||
</div>
|
||||
@endif
|
||||
</div>
|
||||
|
||||
{{-- Hasil Akhir --}}
|
||||
@if($tracking === 'selesai')
|
||||
<div class="sp-card">
|
||||
<div class="sp-head"><div class="bar" style="background:#10b981;"></div><h3>Hasil Akhir Bantuan</h3></div>
|
||||
<div class="sp-body" style="display:flex;flex-direction:column;gap:10px;">
|
||||
@if($st === 'disetujui')
|
||||
<div style="background:#f0fdf4;border:1.5px solid #bbf7d0;border-radius:12px;padding:10px 12px;">
|
||||
<div style="font-size:12px;font-weight:800;color:#166534;">Selamat, data Anda diterima.</div>
|
||||
<div style="font-size:11px;color:#166534;margin-top:3px;">Warga ini termasuk penerima bantuan setelah proses validasi dan filterisasi kuota.</div>
|
||||
</div>
|
||||
@elseif($st === 'ditolak')
|
||||
<div style="background:#fff1f2;border:1.5px solid #fecdd3;border-radius:12px;padding:10px 12px;">
|
||||
<div style="font-size:12px;font-weight:800;color:#9f1239;">Data tidak masuk penerima final.</div>
|
||||
<div style="font-size:11px;color:#9f1239;margin-top:3px;">Warga ini belum terpilih pada penetapan penerima bantuan untuk periode ini.</div>
|
||||
</div>
|
||||
@else
|
||||
<div style="background:#fffbeb;border:1.5px solid #fde68a;border-radius:12px;padding:10px 12px;">
|
||||
<div style="font-size:12px;font-weight:800;color:#b45309;">Hasil akhir belum ditetapkan.</div>
|
||||
<div style="font-size:11px;color:#b45309;margin-top:3px;">Masih menunggu keputusan akhir dari admin kelurahan.</div>
|
||||
</div>
|
||||
{{-- Ringkasan --}}
|
||||
@if(!empty($explanation['summary']))
|
||||
<div class="ringkasan-box">
|
||||
<div class="text-xs font-semibold uppercase tracking-wider text-sky-700 mb-1.5">Ringkasan</div>
|
||||
<p class="text-sm text-sky-900 leading-relaxed">{{ $explanation['summary'] }}</p>
|
||||
</div>
|
||||
@endif
|
||||
|
||||
</div>
|
||||
</div>
|
||||
@endif
|
||||
|
||||
{{-- Zona Bahaya --}}
|
||||
@if($tracking === 'draft')
|
||||
<div class="sp-card" style="border-color:#fecdd3;">
|
||||
<div class="sp-head" style="background:#fff1f2;border-color:#fecdd3;"><div class="bar" style="background:#f43f5e;"></div><h3 style="color:#9f1239;">Zona Bahaya</h3></div>
|
||||
<div class="sp-body">
|
||||
<p style="font-size:11.5px;color:#6b7280;margin-bottom:10px;">Hapus data ini secara permanen. Tindakan ini <strong style="color:#374151;">tidak dapat dibatalkan</strong>.</p>
|
||||
<form action="{{ route('rt.calon-penerima.destroy', $calonPenerima->id) }}" method="POST" onsubmit="return confirm('Yakin ingin menghapus data {{ $calonPenerima->nama_lengkap }}?')">
|
||||
{{-- ZONA BAHAYA --}}
|
||||
@if($currentStatus === 'draft')
|
||||
<div class="zona-bahaya">
|
||||
<div class="zona-bahaya-head">
|
||||
<div class="detail-card-accent" style="background:#ef4444;"></div>
|
||||
<span class="detail-card-title" style="color:#b91c1c;">Zona Bahaya</span>
|
||||
</div>
|
||||
<div class="detail-card-body">
|
||||
<p class="text-sm text-gray-500 mb-3">
|
||||
Hapus data ini secara permanen. Tindakan ini <strong class="text-gray-700">tidak dapat dibatalkan</strong>.
|
||||
</p>
|
||||
<form action="{{ route('rt.calon-penerima.destroy', $calonPenerima->id) }}" method="POST"
|
||||
onsubmit="return confirm('Yakin ingin menghapus data {{ $calonPenerima->nama_lengkap }}?')">
|
||||
@csrf @method('DELETE')
|
||||
<button type="submit" style="width:100%;display:inline-flex;align-items:center;justify-content:center;gap:6px;padding:9px 16px;background:#e11d48;color:#fff;font-size:12px;font-weight:700;border:none;border-radius:10px;cursor:pointer;box-shadow:0 2px 8px rgba(225,29,72,.22);">
|
||||
<svg width="13" height="13" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16"/></svg>
|
||||
<button type="submit"
|
||||
class="w-full inline-flex items-center justify-center gap-2 px-4 py-2.5 text-sm font-semibold rounded-xl bg-red-600 text-white hover:bg-red-700 transition">
|
||||
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16"/></svg>
|
||||
Hapus Data
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
@endif
|
||||
@endif
|
||||
|
||||
</div>{{-- end kanan --}}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>{{-- end .mg --}}
|
||||
</div>
|
||||
|
||||
{{-- LIGHTBOX --}}
|
||||
<div id="lightbox" class="fixed inset-0 z-50 hidden" style="background:rgba(0,0,0,.8);" onclick="closeLightbox()">
|
||||
<div class="flex items-center justify-center h-full p-4">
|
||||
<img id="lightboxImg" src="" alt="Foto" class="max-w-full max-h-full rounded-xl object-contain shadow-2xl">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
function openFoto(src) {
|
||||
document.getElementById('lightboxImg').src = src;
|
||||
document.getElementById('lightbox').classList.remove('hidden');
|
||||
}
|
||||
function closeLightbox() {
|
||||
document.getElementById('lightbox').classList.add('hidden');
|
||||
}
|
||||
document.addEventListener('keydown', e => { if (e.key === 'Escape') closeLightbox(); });
|
||||
</script>
|
||||
</x-app-layout>
|
||||
|
||||
</x-app-layout>
|
||||
|
|
@ -48,45 +48,56 @@ function updateClock(){
|
|||
.recent-item:hover{background:#f0f7ff;}
|
||||
.recent-item:last-child{border-bottom:none;}
|
||||
|
||||
/* ── STAT GRID ── */
|
||||
.rt-stat-grid{
|
||||
display:grid;
|
||||
grid-template-columns:repeat(4,1fr);
|
||||
gap:12px;
|
||||
}
|
||||
@media(max-width:860px){
|
||||
.rt-stat-grid{grid-template-columns:repeat(2,1fr);}
|
||||
}
|
||||
@media(max-width:380px){
|
||||
.rt-stat-grid{grid-template-columns:1fr;}
|
||||
}
|
||||
.rt-stat-grid{display:grid;grid-template-columns:repeat(4,1fr);gap:12px;}
|
||||
@media(max-width:860px){.rt-stat-grid{grid-template-columns:repeat(2,1fr);}}
|
||||
|
||||
/* ── MAIN GRID ── */
|
||||
.rt-main-grid{
|
||||
display:grid;
|
||||
grid-template-columns:2fr 1fr;
|
||||
gap:14px;
|
||||
align-items:start;
|
||||
}
|
||||
@media(max-width:1024px){
|
||||
.rt-main-grid{grid-template-columns:1fr;}
|
||||
}
|
||||
.rt-main-grid{display:grid;grid-template-columns:2fr 1fr;gap:14px;align-items:start;}
|
||||
@media(max-width:1024px){.rt-main-grid{grid-template-columns:1fr;}}
|
||||
|
||||
/* recent-item wrap di layar kecil */
|
||||
@media(max-width:480px){
|
||||
.recent-item{flex-wrap:wrap;gap:6px;}
|
||||
.recent-item-right{width:100%;display:flex;justify-content:flex-end;}
|
||||
}
|
||||
@media(max-width:500px){#live-clock{font-size:10px;}}
|
||||
|
||||
/* clock kecil di mobile */
|
||||
@media(max-width:500px){
|
||||
#live-clock{font-size:10px;}
|
||||
@keyframes pulse{0%,100%{opacity:1;transform:scale(1);}50%{opacity:.5;transform:scale(.85);}}
|
||||
|
||||
/* ── ALUR BANNER ── */
|
||||
.alur-banner{background:linear-gradient(135deg,#1e40af,#2563eb);border-radius:18px;padding:16px;box-shadow:0 6px 24px rgba(37,99,235,.3);position:relative;overflow:hidden;}
|
||||
.alur-banner::before{content:'';position:absolute;top:-30px;right:-30px;width:120px;height:120px;background:rgba(255,255,255,.06);border-radius:50%;}
|
||||
.alur-banner::after{content:'';position:absolute;bottom:-20px;right:60px;width:80px;height:80px;background:rgba(255,255,255,.04);border-radius:50%;}
|
||||
|
||||
.alur-title{font-size:13px;font-weight:900;color:#fff;margin-bottom:12px;display:flex;align-items:center;gap:7px;}
|
||||
.alur-title svg{width:16px;height:16px;stroke:#93c5fd;flex-shrink:0;}
|
||||
|
||||
/* DESKTOP: flex 1 baris */
|
||||
.alur-steps-wrap{display:flex;align-items:stretch;position:relative;z-index:1;}
|
||||
.alur-arrow-wrap{display:flex;align-items:center;padding:0 5px;flex-shrink:0;}
|
||||
.alur-arrow-wrap svg{width:16px;height:16px;stroke:rgba(255,255,255,.4);}
|
||||
.alur-step-box{flex:1;background:rgba(255,255,255,.1);border:1px solid rgba(255,255,255,.18);border-radius:12px;padding:12px 8px;display:flex;flex-direction:column;align-items:center;text-align:center;gap:5px;}
|
||||
|
||||
.alur-step-num{width:22px;height:22px;border-radius:50%;background:#fff;color:#1e40af;font-size:11px;font-weight:900;display:flex;align-items:center;justify-content:center;flex-shrink:0;}
|
||||
.alur-step-icon{width:34px;height:34px;background:rgba(255,255,255,.15);border-radius:9px;display:flex;align-items:center;justify-content:center;}
|
||||
.alur-step-icon svg{width:17px;height:17px;stroke:#fff;}
|
||||
.alur-step-label{font-size:11.5px;font-weight:800;color:#fff;line-height:1.3;}
|
||||
.alur-step-sub{font-size:10px;color:rgba(191,219,254,.85);line-height:1.4;}
|
||||
|
||||
/* MOBILE: grid 2x2, panah disembunyikan */
|
||||
@media(max-width:640px){
|
||||
.alur-banner{padding:13px 12px;}
|
||||
.alur-title{font-size:11px;margin-bottom:10px;}
|
||||
.alur-steps-wrap{display:grid;grid-template-columns:1fr 1fr;gap:7px;}
|
||||
.alur-arrow-wrap{display:none;}
|
||||
.alur-step-box{flex:none;padding:10px 7px;gap:4px;border-radius:10px;}
|
||||
.alur-step-num{width:19px;height:19px;font-size:10px;}
|
||||
.alur-step-icon{width:28px;height:28px;border-radius:7px;}
|
||||
.alur-step-icon svg{width:14px;height:14px;}
|
||||
.alur-step-label{font-size:10.5px;}
|
||||
.alur-step-sub{font-size:9px;color:rgba(191,219,254,.8);}
|
||||
}
|
||||
|
||||
@keyframes pulse{
|
||||
0%,100%{opacity:1;transform:scale(1);}
|
||||
50%{opacity:.5;transform:scale(.85);}
|
||||
}
|
||||
.alur-step-box.active{background:rgba(255,255,255,.22);border-color:rgba(255,255,255,.5);box-shadow:0 0 0 2px rgba(255,255,255,.25);}
|
||||
.alur-step-box.done{background:rgba(16,185,129,.25);border-color:rgba(167,243,208,.4);}
|
||||
</style>
|
||||
|
||||
<div style="display:flex;flex-direction:column;gap:14px;">
|
||||
|
|
@ -102,9 +113,67 @@ function updateClock(){
|
|||
</div>
|
||||
</div>
|
||||
|
||||
{{-- ── BANNER ALUR PENDATAAN ── --}}
|
||||
<div class="alur-banner">
|
||||
<div class="alur-title">
|
||||
<svg fill="none" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 16h-1v-4h-1m1-4h.01M12 3a9 9 0 100 18A9 9 0 0012 3z"/></svg>
|
||||
Panduan: Cara Mendaftarkan & Melihat Hasil Warga
|
||||
</div>
|
||||
<div class="alur-steps-wrap">
|
||||
|
||||
<div class="alur-step-box">
|
||||
<div class="alur-step-num">1</div>
|
||||
<div class="alur-step-icon">
|
||||
<svg fill="none" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M18 9v3m0 0v3m0-3h3m-3 0h-3m-2-5a4 4 0 11-8 0 4 4 0 018 0zM3 20a6 6 0 0112 0v1H3v-1z"/></svg>
|
||||
</div>
|
||||
<div class="alur-step-label">Input Data Warga</div>
|
||||
<div class="alur-step-sub">Klik "Pendataan" lalu "Tambah Data" untuk mendaftarkan warga</div>
|
||||
</div>
|
||||
|
||||
<div class="alur-arrow-wrap">
|
||||
<svg fill="none" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5l7 7-7 7"/></svg>
|
||||
</div>
|
||||
|
||||
<div class="alur-step-box">
|
||||
<div class="alur-step-num">2</div>
|
||||
<div class="alur-step-icon">
|
||||
<svg fill="none" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 12a3 3 0 11-6 0 3 3 0 016 0z"/><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M2.458 12C3.732 7.943 7.523 5 12 5c4.478 0 8.268 2.943 9.542 7-1.274 4.057-5.064 7-9.542 7-4.477 0-8.268-2.943-9.542-7z"/></svg>
|
||||
</div>
|
||||
<div class="alur-step-label">Cek Detail Warga</div>
|
||||
<div class="alur-step-sub">Klik tombol "Detail" pada data warga untuk melihat informasi lengkap</div>
|
||||
</div>
|
||||
|
||||
<div class="alur-arrow-wrap">
|
||||
<svg fill="none" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5l7 7-7 7"/></svg>
|
||||
</div>
|
||||
|
||||
<div class="alur-step-box">
|
||||
<div class="alur-step-num">3</div>
|
||||
<div class="alur-step-icon">
|
||||
<svg fill="none" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z"/></svg>
|
||||
</div>
|
||||
<div class="alur-step-label">Ajukan ke perangkat desa</div>
|
||||
<div class="alur-step-sub">Jika data sudah benar, klik "Ajukan" untuk dikirim ke admin perangkat desa</div>
|
||||
</div>
|
||||
|
||||
<div class="alur-arrow-wrap">
|
||||
<svg fill="none" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5l7 7-7 7"/></svg>
|
||||
</div>
|
||||
|
||||
<div class="alur-step-box">
|
||||
<div class="alur-step-num">4</div>
|
||||
<div class="alur-step-icon">
|
||||
<svg fill="none" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2"/></svg>
|
||||
</div>
|
||||
<div class="alur-step-label">Lihat Hasil di Laporan</div>
|
||||
<div class="alur-step-sub">Hasil akhir Diterima / Ditolak bisa dilihat di menu "Laporan"</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{-- STAT CARDS --}}
|
||||
<div class="rt-stat-grid">
|
||||
|
||||
@php
|
||||
$rtCards = [
|
||||
['label'=>'Total Warga','val'=>$stats['total_input'],'grad'=>'#2563eb,#1d4ed8','shadow'=>'rgba(37,99,235,.3)','tc'=>'rgba(191,219,254,.9)','icon'=>'M17 20h5v-2a3 3 0 00-5.356-1.857M17 20H7m10 0v-2c0-.656-.126-1.283-.356-1.857M7 20H2v-2a3 3 0 015.356-1.857M7 20v-2c0-.656.126-1.283.356-1.857m0 0a5.002 5.002 0 019.288 0M15 7a3 3 0 11-6 0 3 3 0 016 0z'],
|
||||
|
|
@ -113,7 +182,6 @@ function updateClock(){
|
|||
['label'=>'Ditolak','val'=>$stats['ditolak'],'grad'=>'#f43f5e,#e11d48','shadow'=>'rgba(244,63,94,.3)','tc'=>'rgba(254,205,211,.9)','icon'=>'M10 14l2-2m0 0l2-2m-2 2l-2-2m2 2l2 2m7-2a9 9 0 11-18 0 9 9 0 0118 0z'],
|
||||
];
|
||||
@endphp
|
||||
|
||||
@foreach($rtCards as $c)
|
||||
<div style="position:relative;background:linear-gradient(135deg,{{ $c['grad'] }});color:#fff;border-radius:16px;padding:16px;display:flex;align-items:center;justify-content:space-between;overflow:hidden;box-shadow:0 6px 20px {{ $c['shadow'] }};">
|
||||
<div class="sc-blob1"></div><div class="sc-blob2"></div>
|
||||
|
|
@ -126,7 +194,6 @@ function updateClock(){
|
|||
</div>
|
||||
</div>
|
||||
@endforeach
|
||||
|
||||
</div>
|
||||
|
||||
{{-- GRID: Terbaru + Aksi --}}
|
||||
|
|
@ -181,7 +248,6 @@ function updateClock(){
|
|||
<h3 style="font-size:13px;font-weight:700;color:#1e293b;">Aksi Cepat</h3>
|
||||
</div>
|
||||
<div style="padding:12px;display:flex;flex-direction:column;gap:10px;">
|
||||
|
||||
<a href="{{ route('rt.calon-penerima.create') }}" class="aksi-btn" style="background:#eff6ff;border-color:#bfdbfe;">
|
||||
<div class="aksi-icon" style="background:linear-gradient(135deg,#3b82f6,#2563eb);box-shadow:0 4px 12px rgba(37,99,235,.28);">
|
||||
<svg viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M18 9v3m0 0v3m0-3h3m-3 0h-3m-2-5a4 4 0 11-8 0 4 4 0 018 0zM3 20a6 6 0 0112 0v1H3v-1z"/></svg>
|
||||
|
|
@ -191,7 +257,6 @@ function updateClock(){
|
|||
<p style="font-size:10.5px;color:#64748b;margin-top:1px;">Tambah data calon penerima</p>
|
||||
</div>
|
||||
</a>
|
||||
|
||||
<a href="{{ route('rt.laporan.index') }}" class="aksi-btn" style="background:#f0fdf4;border-color:#bbf7d0;">
|
||||
<div class="aksi-icon" style="background:linear-gradient(135deg,#10b981,#059669);box-shadow:0 4px 12px rgba(16,185,129,.28);">
|
||||
<svg viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2"/></svg>
|
||||
|
|
@ -201,8 +266,6 @@ function updateClock(){
|
|||
<p style="font-size:10.5px;color:#64748b;margin-top:1px;">Hasil akhir dari admin</p>
|
||||
</div>
|
||||
</a>
|
||||
|
||||
{{-- Skor rata-rata --}}
|
||||
<div style="padding:14px;background:linear-gradient(135deg,#1e40af,#2563eb,#3b82f6);border-radius:13px;box-shadow:0 6px 20px rgba(37,99,235,.3);position:relative;overflow:hidden;">
|
||||
<div style="position:absolute;top:-14px;right:-14px;width:70px;height:70px;background:rgba(255,255,255,.08);border-radius:50%;pointer-events:none;"></div>
|
||||
<div style="position:relative;z-index:1;">
|
||||
|
|
@ -213,12 +276,9 @@ function updateClock(){
|
|||
<div style="width:100%;height:4px;background:rgba(255,255,255,.2);border-radius:99px;overflow:hidden;margin-bottom:6px;">
|
||||
<div style="height:100%;border-radius:99px;background:rgba(255,255,255,.7);width:{{ min($avgProb,100) }}%;"></div>
|
||||
</div>
|
||||
<p style="font-size:10px;color:rgba(191,219,254,.75);font-weight:500;line-height:1.5;">
|
||||
Rata-rata prediksi, bukan keputusan akhir
|
||||
</p>
|
||||
<p style="font-size:10px;color:rgba(191,219,254,.75);font-weight:500;line-height:1.5;">Rata-rata prediksi, bukan keputusan akhir</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@
|
|||
<div style="display:flex;align-items:center;justify-content:space-between;gap:12px;flex-wrap:wrap;">
|
||||
<div>
|
||||
<h2 style="font-size:16px;font-weight:800;color:#0f172a;letter-spacing:-.02em;">Laporan Hasil Kelayakan</h2>
|
||||
<p style="font-size:11px;color:#94a3b8;margin-top:2px;">Data hasil akhir verifikasi warga yang telah dikirim admin kelurahan</p>
|
||||
<p style="font-size:11px;color:#94a3b8;margin-top:2px;">Data hasil akhir verifikasi warga yang telah dikirim admin perangkat desa</p>
|
||||
</div>
|
||||
</div>
|
||||
</x-slot>
|
||||
|
|
@ -272,7 +272,7 @@
|
|||
@endforelse
|
||||
</div>
|
||||
|
||||
<div class="tbl-foot"><span class="credit">SiBantuDes · Kelurahan Ngerong</span></div>
|
||||
<div class="tbl-foot"><span class="credit">SiBantuDes · Desa Ngerong</span></div>
|
||||
</div>
|
||||
|
||||
</x-app-layout>
|
||||
File diff suppressed because one or more lines are too long
|
|
@ -2,7 +2,10 @@
|
|||
|
||||
use Illuminate\Foundation\Inspiring;
|
||||
use Illuminate\Support\Facades\Artisan;
|
||||
use Illuminate\Support\Facades\Schedule;
|
||||
|
||||
Artisan::command('inspire', function () {
|
||||
$this->comment(Inspiring::quote());
|
||||
})->purpose('Display an inspiring quote');
|
||||
|
||||
Schedule::command('blt:reset-bulanan')->monthlyOn(1, '01:00');
|
||||
168
routes/web.php
168
routes/web.php
|
|
@ -6,6 +6,7 @@
|
|||
|
||||
use App\Models\CalonPenerima;
|
||||
use App\Models\Dusun;
|
||||
use App\Models\PenerimaFinal;
|
||||
|
||||
use App\Http\Controllers\LandingPageController;
|
||||
|
||||
|
|
@ -157,7 +158,8 @@
|
|||
$q = trim((string) $request->query('q', ''));
|
||||
|
||||
$wargasQuery = CalonPenerima::with(['rt.dusun', 'prediksiKelayakan', 'user'])
|
||||
->whereIn('tracking_status', ['terkirim', 'sedang_validasi', 'selesai'])
|
||||
->whereIn('tracking_status', ['terkirim', 'sedang_validasi', 'selesai', 'ditinjau_ulang'])
|
||||
->whereNull('arsip_tahun')
|
||||
->latest();
|
||||
|
||||
if ($q !== '') {
|
||||
|
|
@ -177,25 +179,36 @@
|
|||
return view('admin.data-warga', compact('dusuns', 'wargas'));
|
||||
})->name('data-warga');
|
||||
|
||||
// TAMBAHAN: BERSIHKAN DATA WARGA AKTIF
|
||||
Route::delete('/data-warga/bersihkan', function (Request $request) {
|
||||
// LIHAT ARSIP — harus di atas semua route {id} agar tidak bentrok
|
||||
Route::get('/data-warga/arsip', function (Request $request) {
|
||||
$user = $request->user();
|
||||
|
||||
if (!$user || $user->role !== 'admin') {
|
||||
abort(403, 'Akses ditolak: hanya Admin.');
|
||||
}
|
||||
|
||||
DB::transaction(function () {
|
||||
// hapus hasil prediksi yang terkait data aktif
|
||||
DB::table('prediksi_kelayakans')->delete();
|
||||
$tahun = $request->query('tahun', now()->year);
|
||||
$q = trim((string) $request->query('q', ''));
|
||||
$daftarTahun = CalonPenerima::whereNotNull('arsip_tahun')
|
||||
->distinct()
|
||||
->orderByDesc('arsip_tahun')
|
||||
->pluck('arsip_tahun');
|
||||
|
||||
// hapus data pengajuan aktif
|
||||
DB::table('calon_penerimas')->delete();
|
||||
});
|
||||
$arsipQuery = CalonPenerima::with(['rt.dusun', 'prediksiKelayakan', 'user'])
|
||||
->where('arsip_tahun', $tahun)
|
||||
->latest();
|
||||
|
||||
return redirect()->route('admin.data-warga')
|
||||
->with('success', 'Semua data warga aktif berhasil dibersihkan. Arsip laporan tetap aman.');
|
||||
})->name('data-warga.bersihkan');
|
||||
if ($q !== '') {
|
||||
$arsipQuery->where(function ($sub) use ($q) {
|
||||
$sub->where('nik', 'like', "%{$q}%")
|
||||
->orWhere('nama_lengkap', 'like', "%{$q}%");
|
||||
});
|
||||
}
|
||||
|
||||
$arsips = $arsipQuery->paginate(10)->withQueryString();
|
||||
|
||||
return view('admin.data-warga-arsip', compact('arsips', 'tahun', 'daftarTahun'));
|
||||
})->name('data-warga.arsip');
|
||||
|
||||
// MULAI VALIDASI
|
||||
Route::post('/data-warga/{id}/mulai-validasi', function (Request $request, $id) {
|
||||
|
|
@ -207,7 +220,7 @@
|
|||
|
||||
$warga = CalonPenerima::findOrFail($id);
|
||||
|
||||
if ($warga->tracking_status !== 'terkirim') {
|
||||
if (!in_array($warga->tracking_status, ['terkirim', 'ditinjau_ulang'])) {
|
||||
return back()->with('error', 'Data ini belum bisa masuk tahap validasi.');
|
||||
}
|
||||
|
||||
|
|
@ -244,6 +257,132 @@
|
|||
return back()->with('success', 'Hasil validasi berhasil dikirim ke RT.');
|
||||
})->name('data-warga.selesai-validasi');
|
||||
|
||||
// TERIMA LANGSUNG
|
||||
Route::post('/data-warga/{id}/terima', function (Request $request, $id) {
|
||||
$user = $request->user();
|
||||
|
||||
if (!$user || $user->role !== 'admin') {
|
||||
abort(403, 'Akses ditolak: hanya Admin.');
|
||||
}
|
||||
|
||||
$warga = CalonPenerima::findOrFail($id);
|
||||
|
||||
if (!in_array($warga->tracking_status, ['terkirim', 'sedang_validasi', 'ditinjau_ulang'])) {
|
||||
return back()->with('error', 'Status tidak memungkinkan untuk diterima langsung.');
|
||||
}
|
||||
|
||||
$warga->update([
|
||||
'tracking_status' => 'selesai',
|
||||
'status_verifikasi' => 'disetujui',
|
||||
]);
|
||||
|
||||
return back()->with('success', 'Warga berhasil diterima sebagai penerima BLT-DD.');
|
||||
})->name('data-warga.terima');
|
||||
|
||||
// TOLAK LANGSUNG
|
||||
Route::post('/data-warga/{id}/tolak', function (Request $request, $id) {
|
||||
$user = $request->user();
|
||||
|
||||
if (!$user || $user->role !== 'admin') {
|
||||
abort(403, 'Akses ditolak: hanya Admin.');
|
||||
}
|
||||
|
||||
$warga = CalonPenerima::findOrFail($id);
|
||||
|
||||
if (!in_array($warga->tracking_status, ['terkirim', 'sedang_validasi', 'ditinjau_ulang'])) {
|
||||
return back()->with('error', 'Status tidak memungkinkan untuk ditolak langsung.');
|
||||
}
|
||||
|
||||
$warga->update([
|
||||
'tracking_status' => 'selesai',
|
||||
'status_verifikasi' => 'ditolak',
|
||||
]);
|
||||
|
||||
// Otomatis catat sebagai penerima final dengan status ditolak,
|
||||
// agar langsung muncul di Laporan dan tidak ikut Filterisasi.
|
||||
PenerimaFinal::updateOrCreate(
|
||||
[
|
||||
'nik' => $warga->nik,
|
||||
'periode_bantuan' => '2026 Triwulan 1',
|
||||
],
|
||||
[
|
||||
'nama_lengkap' => $warga->nama_lengkap ?? '-',
|
||||
'no_kk' => $warga->no_kk ?? '-',
|
||||
'rt_id' => $warga->rt_id,
|
||||
'nomor_rt' => $warga->rt?->nomor_rt ?? null,
|
||||
'nama_dusun' => $warga->rt?->dusun?->nama_dusun ?? '-',
|
||||
'pekerjaan' => $warga->pekerjaan ?? '-',
|
||||
'penghasilan' => $warga->penghasilan ?? 0,
|
||||
'jumlah_tanggungan' => $warga->jumlah_tanggungan ?? 0,
|
||||
'aset_kepemilikan' => $warga->aset_kepemilikan ?? '-',
|
||||
'kondisi_rumah' => $warga->kondisi_rumah ?? 'Tidak Diketahui',
|
||||
'meteran_listrik' => $warga->meteran_listrik ?? 'Tidak Diketahui',
|
||||
'sumber_air' => $warga->sumber_air ?? 'Tidak Diketahui',
|
||||
'bantuan_lain' => $warga->bantuan_lain ?? 'tidak',
|
||||
'usia' => $warga->usia ?? 0,
|
||||
'probability' => $warga->prediksiKelayakan->probability ?? 0,
|
||||
'status_verifikasi' => 'ditolak',
|
||||
'tanggal_penetapan' => now()->toDateString(),
|
||||
'periode_bantuan' => '2026 Triwulan 1',
|
||||
'jumlah_bantuan' => 300000,
|
||||
]
|
||||
);
|
||||
|
||||
return back()->with('success', 'Pengajuan warga berhasil ditolak.');
|
||||
})->name('data-warga.tolak');
|
||||
|
||||
// AKTIFKAN KEMBALI
|
||||
Route::post('/data-warga/{id}/aktifkan-kembali', function (Request $request, $id) {
|
||||
$user = $request->user();
|
||||
|
||||
if (!$user || $user->role !== 'admin') {
|
||||
abort(403, 'Akses ditolak: hanya Admin.');
|
||||
}
|
||||
|
||||
$warga = CalonPenerima::findOrFail($id);
|
||||
|
||||
if ($warga->tracking_status !== 'selesai') {
|
||||
return back()->with('error', 'Hanya data yang sudah selesai yang bisa diaktifkan kembali.');
|
||||
}
|
||||
|
||||
// Jika warga sebelumnya tercatat sebagai PenerimaFinal dengan status ditolak
|
||||
// (hasil tolak langsung dari Data Warga), hapus agar tidak nyangkut di Laporan
|
||||
// saat diaktifkan kembali untuk ditinjau ulang.
|
||||
PenerimaFinal::where('nik', $warga->nik)
|
||||
->where('periode_bantuan', '2026 Triwulan 1')
|
||||
->where('status_verifikasi', 'ditolak')
|
||||
->delete();
|
||||
|
||||
$warga->update([
|
||||
'tracking_status' => 'ditinjau_ulang',
|
||||
'status_verifikasi' => 'pending',
|
||||
'arsip_tahun' => null,
|
||||
]);
|
||||
|
||||
return back()->with('success', 'Pengajuan warga berhasil diaktifkan kembali untuk ditinjau ulang.');
|
||||
})->name('data-warga.aktifkan-kembali');
|
||||
|
||||
// ARSIPKAN
|
||||
Route::post('/data-warga/{id}/arsipkan', function (Request $request, $id) {
|
||||
$user = $request->user();
|
||||
|
||||
if (!$user || $user->role !== 'admin') {
|
||||
abort(403, 'Akses ditolak: hanya Admin.');
|
||||
}
|
||||
|
||||
$warga = CalonPenerima::findOrFail($id);
|
||||
|
||||
if ($warga->tracking_status !== 'selesai') {
|
||||
return back()->with('error', 'Hanya data selesai yang bisa diarsipkan.');
|
||||
}
|
||||
|
||||
$warga->update([
|
||||
'arsip_tahun' => now()->year,
|
||||
]);
|
||||
|
||||
return back()->with('success', 'Data berhasil diarsipkan ke tahun ' . now()->year . '.');
|
||||
})->name('data-warga.arsipkan');
|
||||
|
||||
// DATA AKUN
|
||||
Route::get('/data-akun', [AdminController::class, 'dataAkun'])->name('data-akun');
|
||||
Route::post('/data-akun/{id}/role', [AdminController::class, 'ubahRole'])->name('data-akun.role');
|
||||
|
|
@ -254,12 +393,15 @@
|
|||
Route::get('/filterisasi', [FilterisasiController::class, 'index'])->name('filterisasi');
|
||||
Route::post('/filterisasi/tetapkan', [FilterisasiController::class, 'tetapkan'])->name('filterisasi.tetapkan');
|
||||
Route::post('/filterisasi/reset', [FilterisasiController::class, 'resetDusun'])->name('filterisasi.reset');
|
||||
Route::post('/filterisasi/simpan-periode', [FilterisasiController::class, 'simpanPeriode'])->name('filterisasi.simpanPeriode');
|
||||
Route::delete('/filterisasi/hapus-periode/{id}', [FilterisasiController::class, 'hapusPeriode'])->name('filterisasi.hapusPeriode');
|
||||
|
||||
// LAPORAN ADMIN
|
||||
Route::get('/laporan', [LaporanController::class, 'index'])->name('laporan');
|
||||
Route::get('/laporan/pdf', [LaporanController::class, 'exportPdf'])->name('laporan.pdf');
|
||||
Route::get('/laporan/excel', [LaporanController::class, 'exportExcel'])->name('laporan.excel');
|
||||
Route::post('/laporan/kirim-ke-rt', [LaporanController::class, 'kirimKeRt'])->name('laporan.kirim-ke-rt');
|
||||
Route::post('/laporan/reset-manual', [LaporanController::class, 'resetManual'])->name('laporan.reset-manual');
|
||||
Route::post('/laporan/upload-publik-pdf', [LaporanController::class, 'uploadPublikPdf'])->name('laporan.upload-publik-pdf');
|
||||
Route::delete('/laporan/publik-pdf/{id}', [LaporanController::class, 'hapusPublikPdf'])->name('laporan.hapus-publik-pdf');
|
||||
Route::delete('/laporan/bersihkan-riwayat-selesai', [LaporanController::class, 'bersihkanRiwayatSelesai'])
|
||||
|
|
|
|||
Loading…
Reference in New Issue