perbaikan view controller dan migration untuk admin dan rt
This commit is contained in:
parent
8b9581f103
commit
b0140be0a8
|
|
@ -14,13 +14,18 @@ class FilterisasiController extends Controller
|
|||
public function index(Request $request)
|
||||
{
|
||||
$user = $request->user();
|
||||
if (!$user || $user->role !== 'admin') abort(403, 'Akses ditolak: hanya Admin.');
|
||||
if (!$user || $user->role !== 'admin') {
|
||||
abort(403, 'Akses ditolak: hanya Admin.');
|
||||
}
|
||||
|
||||
$dusuns = Dusun::orderBy('nama_dusun')->get();
|
||||
|
||||
$dusunId = (int) $request->query('dusun_id', 0);
|
||||
$kuota = (int) $request->query('kuota', 7);
|
||||
if ($kuota <= 0) $kuota = 7;
|
||||
|
||||
if ($kuota <= 0) {
|
||||
$kuota = 7;
|
||||
}
|
||||
|
||||
$candidates = collect();
|
||||
$pickedIds = collect();
|
||||
|
|
@ -28,10 +33,11 @@ public function index(Request $request)
|
|||
if ($dusunId > 0) {
|
||||
$query = CalonPenerima::query()
|
||||
->with(['rt.dusun', 'prediksiKelayakan'])
|
||||
->where('status_verifikasi', 'disetujui') // ✅ ONLY DISETUJUI
|
||||
->whereHas('rt', fn($q) => $q->where('dusun_id', $dusunId));
|
||||
->whereIn('tracking_status', ['sedang_validasi', 'selesai'])
|
||||
->whereHas('rt', function ($q) use ($dusunId) {
|
||||
$q->where('dusun_id', $dusunId);
|
||||
});
|
||||
|
||||
// urut probability DESC (null jadi 0)
|
||||
$query->leftJoin('prediksi_kelayakans', 'prediksi_kelayakans.calon_penerima_id', '=', 'calon_penerimas.id')
|
||||
->select('calon_penerimas.*', DB::raw('COALESCE(prediksi_kelayakans.probability, 0) as prob'))
|
||||
->orderByDesc('prob')
|
||||
|
|
@ -39,7 +45,6 @@ public function index(Request $request)
|
|||
|
||||
$candidates = $query->paginate(15)->withQueryString();
|
||||
|
||||
// tandai yang sudah masuk penerima_final
|
||||
$pickedIds = PenerimaFinal::whereIn('calon_penerima_id', $candidates->pluck('id')->all())
|
||||
->pluck('calon_penerima_id');
|
||||
}
|
||||
|
|
@ -50,7 +55,9 @@ public function index(Request $request)
|
|||
public function tetapkan(Request $request)
|
||||
{
|
||||
$user = $request->user();
|
||||
if (!$user || $user->role !== 'admin') abort(403, 'Akses ditolak: hanya Admin.');
|
||||
if (!$user || $user->role !== 'admin') {
|
||||
abort(403, 'Akses ditolak: hanya Admin.');
|
||||
}
|
||||
|
||||
$data = $request->validate([
|
||||
'dusun_id' => 'required|exists:dusuns,id',
|
||||
|
|
@ -61,32 +68,65 @@ public function tetapkan(Request $request)
|
|||
$kuota = (int) ($data['kuota'] ?? 7);
|
||||
|
||||
DB::transaction(function () use ($dusunId, $kuota) {
|
||||
|
||||
$top = CalonPenerima::query()
|
||||
->where('status_verifikasi', 'disetujui') // ✅ ONLY DISETUJUI
|
||||
->whereHas('rt', fn($r) => $r->where('dusun_id', $dusunId))
|
||||
// Ambil semua kandidat dalam dusun yang sedang divalidasi / sudah selesai
|
||||
$allCandidates = CalonPenerima::query()
|
||||
->whereIn('tracking_status', ['sedang_validasi', 'selesai'])
|
||||
->whereHas('rt', function ($r) use ($dusunId) {
|
||||
$r->where('dusun_id', $dusunId);
|
||||
})
|
||||
->leftJoin('prediksi_kelayakans', 'prediksi_kelayakans.calon_penerima_id', '=', 'calon_penerimas.id')
|
||||
->select('calon_penerimas.id', DB::raw('COALESCE(prediksi_kelayakans.probability, 0) as prob'))
|
||||
->orderByDesc('prob')
|
||||
->orderByDesc('calon_penerimas.created_at')
|
||||
->limit($kuota)
|
||||
->get();
|
||||
|
||||
foreach ($top as $row) {
|
||||
$topIds = $allCandidates->take($kuota)->pluck('id')->all();
|
||||
$allIds = $allCandidates->pluck('id')->all();
|
||||
|
||||
// Hapus hasil final lama untuk kandidat pada dusun ini
|
||||
if (!empty($allIds)) {
|
||||
PenerimaFinal::whereIn('calon_penerima_id', $allIds)->delete();
|
||||
}
|
||||
|
||||
// Simpan hasil final penerima yang masuk kuota
|
||||
foreach ($topIds as $id) {
|
||||
PenerimaFinal::updateOrCreate(
|
||||
['calon_penerima_id' => $row->id],
|
||||
['dusun_id' => $dusunId] // kalau tabel kamu ga punya dusun_id, bilang ya nanti aku rapikan
|
||||
['calon_penerima_id' => $id],
|
||||
[
|
||||
'tanggal_penetapan' => now()->toDateString(),
|
||||
'periode_bantuan' => date('Y') . ' Triwulan 1',
|
||||
'jumlah_bantuan' => 0,
|
||||
'status_pencairan' => 'belum_cair',
|
||||
'tanggal_pencairan' => null,
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
// Yang masuk kuota = disetujui
|
||||
if (!empty($topIds)) {
|
||||
CalonPenerima::whereIn('id', $topIds)->update([
|
||||
'status_verifikasi' => 'disetujui',
|
||||
]);
|
||||
}
|
||||
|
||||
// Yang tidak masuk kuota = ditolak
|
||||
$notPickedIds = array_diff($allIds, $topIds);
|
||||
if (!empty($notPickedIds)) {
|
||||
CalonPenerima::whereIn('id', $notPickedIds)->update([
|
||||
'status_verifikasi' => 'ditolak',
|
||||
]);
|
||||
}
|
||||
});
|
||||
|
||||
return back()->with('success', "Berhasil menetapkan kuota {$kuota} penerima (status disetujui) untuk dusun terpilih.");
|
||||
return back()->with('success', "Berhasil menetapkan kuota {$kuota} penerima untuk dusun terpilih.");
|
||||
}
|
||||
|
||||
public function resetDusun(Request $request)
|
||||
{
|
||||
$user = $request->user();
|
||||
if (!$user || $user->role !== 'admin') abort(403, 'Akses ditolak: hanya Admin.');
|
||||
if (!$user || $user->role !== 'admin') {
|
||||
abort(403, 'Akses ditolak: hanya Admin.');
|
||||
}
|
||||
|
||||
$data = $request->validate([
|
||||
'dusun_id' => 'required|exists:dusuns,id',
|
||||
|
|
@ -94,7 +134,23 @@ public function resetDusun(Request $request)
|
|||
|
||||
$dusunId = (int) $data['dusun_id'];
|
||||
|
||||
PenerimaFinal::where('dusun_id', $dusunId)->delete();
|
||||
DB::transaction(function () use ($dusunId) {
|
||||
$candidateIds = CalonPenerima::query()
|
||||
->whereHas('rt', function ($r) use ($dusunId) {
|
||||
$r->where('dusun_id', $dusunId);
|
||||
})
|
||||
->whereIn('tracking_status', ['sedang_validasi', 'selesai'])
|
||||
->pluck('id')
|
||||
->all();
|
||||
|
||||
if (!empty($candidateIds)) {
|
||||
PenerimaFinal::whereIn('calon_penerima_id', $candidateIds)->delete();
|
||||
|
||||
CalonPenerima::whereIn('id', $candidateIds)->update([
|
||||
'status_verifikasi' => 'pending',
|
||||
]);
|
||||
}
|
||||
});
|
||||
|
||||
return back()->with('success', 'Hasil filterisasi dusun berhasil di-reset.');
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@
|
|||
namespace App\Http\Controllers\Auth;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Models\RT;
|
||||
use App\Models\User;
|
||||
use Illuminate\Auth\Events\Registered;
|
||||
use Illuminate\Http\RedirectResponse;
|
||||
|
|
@ -19,7 +20,13 @@ class RegisteredUserController extends Controller
|
|||
*/
|
||||
public function create(): View
|
||||
{
|
||||
return view('auth.register');
|
||||
// Ambil semua RT + dusun untuk dropdown di halaman register
|
||||
$rts = RT::with('dusun')
|
||||
->orderBy('dusun_id')
|
||||
->orderBy('nomor_rt')
|
||||
->get();
|
||||
|
||||
return view('auth.register', compact('rts'));
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -31,20 +38,30 @@ public function store(Request $request): RedirectResponse
|
|||
{
|
||||
$request->validate([
|
||||
'name' => ['required', 'string', 'max:255'],
|
||||
'email' => ['required', 'string', 'lowercase', 'email', 'max:255', 'unique:'.User::class],
|
||||
'email' => ['required', 'string', 'lowercase', 'email', 'max:255', 'unique:' . User::class],
|
||||
'password' => ['required', 'confirmed', Rules\Password::defaults()],
|
||||
|
||||
// WAJIB pilih RT biar user punya rt_id
|
||||
'rt_id' => ['required', 'exists:rts,id'],
|
||||
]);
|
||||
|
||||
$user = User::create([
|
||||
'name' => $request->name,
|
||||
'email' => $request->email,
|
||||
'password' => Hash::make($request->password),
|
||||
|
||||
// Set default akun hasil register sebagai RT
|
||||
'role' => 'rt',
|
||||
'rt_id' => $request->rt_id,
|
||||
]);
|
||||
|
||||
event(new Registered($user));
|
||||
|
||||
Auth::login($user);
|
||||
|
||||
// Kamu bisa arahkan ke dashboard RT kalau punya route khusus RT
|
||||
// return redirect()->route('rt.dashboard');
|
||||
|
||||
return redirect(route('dashboard', absolute: false));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -13,7 +13,7 @@
|
|||
|
||||
class CalonPenerimaController extends Controller
|
||||
{
|
||||
protected $mlService;
|
||||
protected MLPredictionService $mlService;
|
||||
|
||||
public function __construct(MLPredictionService $mlService)
|
||||
{
|
||||
|
|
@ -24,10 +24,9 @@ public function index()
|
|||
{
|
||||
$user = Auth::user();
|
||||
|
||||
// KHUSUS RT: hanya data milik RT tersebut
|
||||
$calonPenerimas = CalonPenerima::with(['rt.dusun', 'prediksiKelayakan'])
|
||||
->where('user_id', $user->id)
|
||||
->orderBy('created_at', 'desc')
|
||||
->latest()
|
||||
->paginate(20);
|
||||
|
||||
return view('rt.calon-penerima.index', compact('calonPenerimas'));
|
||||
|
|
@ -35,9 +34,15 @@ public function index()
|
|||
|
||||
public function create()
|
||||
{
|
||||
// RT tidak boleh pilih RT lain
|
||||
// Tapi kita biarkan view create tetap bisa pakai data RT user (kalau mau ditampilkan)
|
||||
$user = Auth::user();
|
||||
|
||||
$rts = RT::where('id', $user->rt_id)->with('dusun')->get();
|
||||
$rts = RT::where('id', $user->rt_id)
|
||||
->with('dusun')
|
||||
->get();
|
||||
|
||||
// fallback kalau data RT user belum ada (harusnya jarang terjadi)
|
||||
if ($rts->isEmpty()) {
|
||||
$rts = RT::with('dusun')->get();
|
||||
}
|
||||
|
|
@ -47,16 +52,27 @@ public function create()
|
|||
|
||||
public function store(Request $request)
|
||||
{
|
||||
$user = Auth::user();
|
||||
|
||||
$myRtId = $user->rt_id ?? null;
|
||||
if (!$myRtId) {
|
||||
return back()->withInput()->with('error', 'Akun RT belum terhubung ke data RT.');
|
||||
}
|
||||
|
||||
$validated = $request->validate([
|
||||
'rt_id' => 'required|exists:rts,id',
|
||||
'no_kk' => 'required|string|max:16',
|
||||
'nik' => 'required|digits:16|unique:calon_penerimas,nik',
|
||||
// rt_id boleh ada di form, tapi akan kita override (anti inject)
|
||||
'rt_id' => 'nullable|exists:rts,id',
|
||||
|
||||
// ✅ NIK & NO KK TIDAK BOLEH DUPLIKAT
|
||||
'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' => 'required|string|max:255',
|
||||
'desa' => 'nullable|string|max:255', // akan kita set otomatis
|
||||
'pekerjaan' => 'required|string',
|
||||
'penghasilan' => 'required|numeric|min:0',
|
||||
'jumlah_tanggungan' => 'required|integer|min:0',
|
||||
|
|
@ -64,12 +80,27 @@ public function store(Request $request)
|
|||
'bantuan_lain' => 'required|in:ya,tidak',
|
||||
'usia' => 'required|integer|min:17|max:100',
|
||||
'status_perkawinan' => 'required|string',
|
||||
], [
|
||||
'nik.unique' => 'NIK sudah pernah diinput.',
|
||||
'no_kk.unique' => 'No. KK sudah pernah diinput.',
|
||||
]);
|
||||
|
||||
// 🔒 Kunci RT dari user login (anti inject)
|
||||
$validated['rt_id'] = $myRtId;
|
||||
|
||||
// 🔒 Kunci Dusun/Desa ikut RT
|
||||
$rt = RT::with('dusun')->find($myRtId);
|
||||
if ($rt && $rt->dusun) {
|
||||
$validated['desa'] = $rt->dusun->nama_dusun;
|
||||
} else {
|
||||
// fallback aman kalau relasi dusun kosong
|
||||
$validated['desa'] = $validated['desa'] ?? '-';
|
||||
}
|
||||
|
||||
DB::beginTransaction();
|
||||
try {
|
||||
$calonPenerima = CalonPenerima::create([
|
||||
'user_id' => Auth::id(),
|
||||
'user_id' => $user->id,
|
||||
'rt_id' => $validated['rt_id'],
|
||||
'no_kk' => $validated['no_kk'],
|
||||
'nik' => $validated['nik'],
|
||||
|
|
@ -87,8 +118,10 @@ public function store(Request $request)
|
|||
'usia' => $validated['usia'],
|
||||
'status_perkawinan' => $validated['status_perkawinan'],
|
||||
'status_verifikasi' => 'pending',
|
||||
'tracking_status' => 'draft',
|
||||
]);
|
||||
|
||||
// 🔮 ML Prediction
|
||||
$predictionData = [
|
||||
'pekerjaan' => $validated['pekerjaan'],
|
||||
'penghasilan' => $validated['penghasilan'],
|
||||
|
|
@ -114,7 +147,8 @@ public function store(Request $request)
|
|||
->with('success', 'Data calon penerima berhasil ditambahkan!');
|
||||
} catch (\Exception $e) {
|
||||
DB::rollBack();
|
||||
return redirect()->back()
|
||||
|
||||
return back()
|
||||
->withInput()
|
||||
->with('error', 'Terjadi kesalahan: ' . $e->getMessage());
|
||||
}
|
||||
|
|
@ -124,50 +158,57 @@ public function show(CalonPenerima $calonPenerima)
|
|||
{
|
||||
$user = Auth::user();
|
||||
|
||||
// pastikan RT hanya bisa lihat miliknya
|
||||
// 🔒 RT hanya boleh lihat data miliknya
|
||||
if ($calonPenerima->user_id !== $user->id) {
|
||||
abort(403, 'Unauthorized action.');
|
||||
}
|
||||
|
||||
$calonPenerima->load(['rt.dusun', 'prediksiKelayakan', 'penerimaFinal']);
|
||||
$calonPenerima->load(['rt.dusun', 'prediksiKelayakan']);
|
||||
|
||||
return view('rt.calon-penerima.show', compact('calonPenerima'));
|
||||
$explanation = $this->getPredictionExplanation($calonPenerima);
|
||||
|
||||
return view('rt.calon-penerima.show', compact('calonPenerima', 'explanation'));
|
||||
}
|
||||
|
||||
public function edit(CalonPenerima $calonPenerima)
|
||||
{
|
||||
$user = Auth::user();
|
||||
|
||||
if ($calonPenerima->user_id !== $user->id || $calonPenerima->status_verifikasi !== 'pending') {
|
||||
abort(403, 'Unauthorized action.');
|
||||
// 🔒 RT hanya bisa edit kalau miliknya + masih draft
|
||||
if ($calonPenerima->user_id !== $user->id || ($calonPenerima->tracking_status ?? 'draft') !== 'draft') {
|
||||
abort(403, 'Data yang sudah diajukan tidak dapat diubah.');
|
||||
}
|
||||
|
||||
$rts = RT::where('id', $user->rt_id)->with('dusun')->get();
|
||||
if ($rts->isEmpty()) {
|
||||
$rts = RT::with('dusun')->get();
|
||||
}
|
||||
|
||||
return view('rt.calon-penerima.edit', compact('calonPenerima', 'rts'));
|
||||
return view('rt.calon-penerima.edit', compact('calonPenerima'));
|
||||
}
|
||||
|
||||
public function update(Request $request, CalonPenerima $calonPenerima)
|
||||
{
|
||||
$user = Auth::user();
|
||||
|
||||
if ($calonPenerima->user_id !== $user->id || $calonPenerima->status_verifikasi !== 'pending') {
|
||||
abort(403, 'Unauthorized action.');
|
||||
// 🔒 RT hanya bisa update kalau miliknya + masih draft
|
||||
if ($calonPenerima->user_id !== $user->id || ($calonPenerima->tracking_status ?? 'draft') !== 'draft') {
|
||||
abort(403, 'Data yang sudah diajukan tidak dapat diubah.');
|
||||
}
|
||||
|
||||
$myRtId = $user->rt_id ?? null;
|
||||
if (!$myRtId) {
|
||||
return back()->withInput()->with('error', 'Akun RT belum terhubung ke data RT.');
|
||||
}
|
||||
|
||||
$validated = $request->validate([
|
||||
'rt_id' => 'required|exists:rts,id',
|
||||
'no_kk' => 'required|string|max:16',
|
||||
'nik' => 'required|digits:16|unique:calon_penerimas,nik,' . $calonPenerima->id,
|
||||
'rt_id' => 'nullable|exists:rts,id',
|
||||
|
||||
// ✅ UNIQUE KECUALI DATA SENDIRI
|
||||
'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' => 'required|string|max:255',
|
||||
'desa' => 'nullable|string|max:255', // akan di-override
|
||||
'pekerjaan' => 'required|string',
|
||||
'penghasilan' => 'required|numeric|min:0',
|
||||
'jumlah_tanggungan' => 'required|integer|min:0',
|
||||
|
|
@ -175,12 +216,30 @@ public function update(Request $request, CalonPenerima $calonPenerima)
|
|||
'bantuan_lain' => 'required|in:ya,tidak',
|
||||
'usia' => 'required|integer|min:17|max:100',
|
||||
'status_perkawinan' => 'required|string',
|
||||
], [
|
||||
'nik.unique' => 'NIK sudah pernah diinput.',
|
||||
'no_kk.unique' => 'No. KK sudah pernah diinput.',
|
||||
]);
|
||||
|
||||
// 🔒 Kunci RT dari user login (anti inject)
|
||||
$validated['rt_id'] = $myRtId;
|
||||
|
||||
// 🔒 Kunci Dusun/Desa ikut RT
|
||||
$rt = RT::with('dusun')->find($myRtId);
|
||||
if ($rt && $rt->dusun) {
|
||||
$validated['desa'] = $rt->dusun->nama_dusun;
|
||||
} else {
|
||||
$validated['desa'] = $validated['desa'] ?? '-';
|
||||
}
|
||||
|
||||
DB::beginTransaction();
|
||||
try {
|
||||
$calonPenerima->update($validated);
|
||||
|
||||
// (Opsional) Kalau kamu mau: update ulang prediksi ML saat data diupdate
|
||||
// Kalau mau aktif, uncomment ini:
|
||||
|
||||
/*
|
||||
$predictionData = [
|
||||
'pekerjaan' => $validated['pekerjaan'],
|
||||
'penghasilan' => $validated['penghasilan'],
|
||||
|
|
@ -193,7 +252,7 @@ public function update(Request $request, CalonPenerima $calonPenerima)
|
|||
$prediction = $this->mlService->getPrediction($predictionData);
|
||||
|
||||
if ($prediction) {
|
||||
$calonPenerima->prediksiKelayakan()->updateOrCreate(
|
||||
PrediksiKelayakan::updateOrCreate(
|
||||
['calon_penerima_id' => $calonPenerima->id],
|
||||
[
|
||||
'probability' => $prediction['probability'],
|
||||
|
|
@ -201,6 +260,7 @@ public function update(Request $request, CalonPenerima $calonPenerima)
|
|||
]
|
||||
);
|
||||
}
|
||||
*/
|
||||
|
||||
DB::commit();
|
||||
|
||||
|
|
@ -208,18 +268,41 @@ public function update(Request $request, CalonPenerima $calonPenerima)
|
|||
->with('success', 'Data calon penerima berhasil diupdate!');
|
||||
} catch (\Exception $e) {
|
||||
DB::rollBack();
|
||||
return redirect()->back()
|
||||
|
||||
return back()
|
||||
->withInput()
|
||||
->with('error', 'Terjadi kesalahan: ' . $e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
public function ajukan(CalonPenerima $calonPenerima)
|
||||
{
|
||||
$user = Auth::user();
|
||||
|
||||
if ($calonPenerima->user_id !== $user->id) {
|
||||
abort(403, 'Unauthorized action.');
|
||||
}
|
||||
|
||||
if (($calonPenerima->tracking_status ?? 'draft') !== 'draft') {
|
||||
return redirect()->route('rt.calon-penerima.index')
|
||||
->with('error', 'Data ini sudah diajukan dan tidak dapat diajukan lagi.');
|
||||
}
|
||||
|
||||
$calonPenerima->update([
|
||||
'tracking_status' => 'terkirim',
|
||||
]);
|
||||
|
||||
return redirect()->route('rt.calon-penerima.index')
|
||||
->with('success', 'Data berhasil diajukan ke admin kelurahan.');
|
||||
}
|
||||
|
||||
public function destroy(CalonPenerima $calonPenerima)
|
||||
{
|
||||
$user = Auth::user();
|
||||
|
||||
if ($calonPenerima->user_id !== $user->id || $calonPenerima->status_verifikasi !== 'pending') {
|
||||
abort(403, 'Unauthorized action.');
|
||||
// 🔒 RT hanya bisa hapus kalau miliknya + masih draft
|
||||
if ($calonPenerima->user_id !== $user->id || ($calonPenerima->tracking_status ?? 'draft') !== 'draft') {
|
||||
abort(403, 'Data yang sudah diajukan tidak dapat dihapus.');
|
||||
}
|
||||
|
||||
$calonPenerima->delete();
|
||||
|
|
@ -227,4 +310,91 @@ public function destroy(CalonPenerima $calonPenerima)
|
|||
return redirect()->route('rt.calon-penerima.index')
|
||||
->with('success', 'Data calon penerima berhasil dihapus!');
|
||||
}
|
||||
|
||||
private function getPredictionExplanation($calonPenerima): array
|
||||
{
|
||||
$positive = [];
|
||||
$negative = [];
|
||||
|
||||
// PEKERJAAN
|
||||
if (strtolower($calonPenerima->pekerjaan) === 'tidak bekerja') {
|
||||
$positive[] = 'Tidak bekerja';
|
||||
} elseif (in_array(strtolower($calonPenerima->pekerjaan), ['buruh harian', 'buruh', 'petani', 'nelayan'])) {
|
||||
$positive[] = 'Pekerjaan tergolong rentan secara ekonomi';
|
||||
} else {
|
||||
$negative[] = 'Memiliki pekerjaan yang relatif lebih stabil';
|
||||
}
|
||||
|
||||
// PENGHASILAN
|
||||
if ($calonPenerima->penghasilan <= 1000000) {
|
||||
$positive[] = 'Penghasilan rendah';
|
||||
} elseif ($calonPenerima->penghasilan <= 2000000) {
|
||||
$positive[] = 'Penghasilan masih tergolong rendah';
|
||||
} else {
|
||||
$negative[] = 'Penghasilan relatif lebih tinggi';
|
||||
}
|
||||
|
||||
// JUMLAH TANGGUNGAN
|
||||
if ($calonPenerima->jumlah_tanggungan >= 4) {
|
||||
$positive[] = 'Jumlah tanggungan banyak';
|
||||
} elseif ($calonPenerima->jumlah_tanggungan >= 2) {
|
||||
$positive[] = 'Jumlah tanggungan cukup banyak';
|
||||
} else {
|
||||
$negative[] = 'Jumlah tanggungan sedikit';
|
||||
}
|
||||
|
||||
// ASET KEPEMILIKAN
|
||||
$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 ($aset === 'tidak ada' || $aset === '-' || $aset === 'tidak punya') {
|
||||
$positive[] = 'Tidak memiliki aset berarti';
|
||||
}
|
||||
|
||||
// BANTUAN LAIN
|
||||
if (strtolower($calonPenerima->bantuan_lain) === 'ya') {
|
||||
$negative[] = 'Sudah menerima bantuan lain';
|
||||
} else {
|
||||
$positive[] = 'Belum menerima bantuan lain';
|
||||
}
|
||||
|
||||
// USIA
|
||||
if ($calonPenerima->usia >= 60) {
|
||||
$positive[] = 'Usia lanjut';
|
||||
} elseif ($calonPenerima->usia >= 45) {
|
||||
$positive[] = 'Usia cukup rentan';
|
||||
}
|
||||
|
||||
// NARASI PENJELASAN
|
||||
$probability = $calonPenerima->prediksiKelayakan->probability ?? 0;
|
||||
$probability = $probability <= 1 ? $probability * 100 : $probability;
|
||||
$probability = number_format($probability, 1);
|
||||
|
||||
$summary = "Nilai kelayakan {$probability}% diperoleh berdasarkan data pekerjaan, penghasilan, jumlah tanggungan, aset kepemilikan, bantuan lain, dan usia.";
|
||||
|
||||
if (count($positive) > 0 && count($negative) > 0) {
|
||||
$summary .= " Sistem menilai terdapat beberapa faktor yang mendukung kelayakan, namun ada juga faktor yang mengurangi nilai kelayakan.";
|
||||
} elseif (count($positive) > 0) {
|
||||
$summary .= " Sebagian besar faktor yang dinilai cenderung mendukung kelayakan penerima bantuan.";
|
||||
} elseif (count($negative) > 0) {
|
||||
$summary .= " Sebagian besar faktor yang dinilai cenderung menurunkan tingkat kelayakan.";
|
||||
}
|
||||
|
||||
return [
|
||||
'positive' => $positive,
|
||||
'negative' => $negative,
|
||||
'summary' => $summary,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
|
@ -10,10 +10,26 @@ 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', 'bantuan_lain',
|
||||
'usia', 'status_perkawinan', 'status_verifikasi', 'catatan_admin',
|
||||
'user_id',
|
||||
'rt_id',
|
||||
'no_kk',
|
||||
'nik',
|
||||
'nama_lengkap',
|
||||
'jenis_kelamin',
|
||||
'tempat_lahir',
|
||||
'tanggal_lahir',
|
||||
'alamat',
|
||||
'desa',
|
||||
'pekerjaan',
|
||||
'penghasilan',
|
||||
'jumlah_tanggungan',
|
||||
'aset_kepemilikan',
|
||||
'bantuan_lain',
|
||||
'usia',
|
||||
'status_perkawinan',
|
||||
'status_verifikasi',
|
||||
'tracking_status', // status tracking proses
|
||||
'catatan_admin',
|
||||
];
|
||||
|
||||
protected $casts = [
|
||||
|
|
|
|||
|
|
@ -2,57 +2,146 @@
|
|||
|
||||
namespace App\Services;
|
||||
|
||||
use Illuminate\Support\Facades\Http;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
|
||||
class MLPredictionService
|
||||
{
|
||||
protected $apiUrl;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->apiUrl = config('services.ml_api.url', 'http://127.0.0.1:5000');
|
||||
}
|
||||
|
||||
/**
|
||||
* Dapatkan prediksi dari ML API
|
||||
* Dapatkan prediksi dari script Python langsung
|
||||
*/
|
||||
public function getPrediction(array $data)
|
||||
{
|
||||
try {
|
||||
/** @var \Illuminate\Http\Client\Response $response */
|
||||
$response = Http::timeout(30)->post($this->apiUrl . '/predict', [
|
||||
'pekerjaan' => $data['pekerjaan'],
|
||||
'penghasilan' => (float) $data['penghasilan'],
|
||||
'jumlah_tanggungan' => (int) $data['jumlah_tanggungan'],
|
||||
'aset_kepemilikan' => $data['aset_kepemilikan'],
|
||||
'bantuan_lain' => $data['bantuan_lain'],
|
||||
'usia' => (int) $data['usia'],
|
||||
]);
|
||||
$payload = [
|
||||
'pekerjaan' => $data['pekerjaan'] ?? '',
|
||||
'penghasilan' => (float) ($data['penghasilan'] ?? 0),
|
||||
'jumlah_tanggungan' => (int) ($data['jumlah_tanggungan'] ?? 0),
|
||||
'aset_kepemilikan' => $data['aset_kepemilikan'] ?? '',
|
||||
'bantuan_lain' => $data['bantuan_lain'] ?? '',
|
||||
'usia' => (int) ($data['usia'] ?? 0),
|
||||
];
|
||||
|
||||
if ($response->successful()) {
|
||||
return $response->json();
|
||||
$jsonPayload = json_encode($payload, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
|
||||
|
||||
$pythonPath = 'python';
|
||||
$scriptPath = base_path('ml/predict.py');
|
||||
|
||||
$descriptorspec = [
|
||||
0 => ['pipe', 'r'], // stdin
|
||||
1 => ['pipe', 'w'], // stdout
|
||||
2 => ['pipe', 'w'], // stderr
|
||||
];
|
||||
|
||||
$process = proc_open(
|
||||
$pythonPath . ' ' . escapeshellarg($scriptPath),
|
||||
$descriptorspec,
|
||||
$pipes,
|
||||
base_path()
|
||||
);
|
||||
|
||||
if (!is_resource($process)) {
|
||||
Log::error('Gagal menjalankan proses Python ML.');
|
||||
return null;
|
||||
}
|
||||
|
||||
Log::error('ML API Error: ' . $response->body());
|
||||
return null;
|
||||
fwrite($pipes[0], $jsonPayload);
|
||||
fclose($pipes[0]);
|
||||
|
||||
} catch (\Exception $e) {
|
||||
Log::error('ML API Exception: ' . $e->getMessage());
|
||||
$output = stream_get_contents($pipes[1]);
|
||||
fclose($pipes[1]);
|
||||
|
||||
$errorOutput = stream_get_contents($pipes[2]);
|
||||
fclose($pipes[2]);
|
||||
|
||||
$exitCode = proc_close($process);
|
||||
|
||||
if ($exitCode !== 0) {
|
||||
Log::error('Python ML exit code bukan 0.', [
|
||||
'exit_code' => $exitCode,
|
||||
'stderr' => $errorOutput,
|
||||
'stdout' => $output,
|
||||
]);
|
||||
return null;
|
||||
}
|
||||
|
||||
$result = json_decode($output, true);
|
||||
|
||||
if (!$result || isset($result['error'])) {
|
||||
Log::error('Hasil prediksi ML tidak valid.', [
|
||||
'output' => $output,
|
||||
'stderr' => $errorOutput,
|
||||
'decoded' => $result,
|
||||
]);
|
||||
return null;
|
||||
}
|
||||
|
||||
return [
|
||||
'probability' => $result['probability'] ?? 0,
|
||||
'recommendation' => $result['recommendation'] ?? 'Tidak Layak',
|
||||
];
|
||||
} catch (\Throwable $e) {
|
||||
Log::error('ML Prediction Exception: ' . $e->getMessage());
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Cek apakah ML API sedang online
|
||||
* Cek apakah script ML bisa dijalankan
|
||||
*/
|
||||
public function healthCheck()
|
||||
{
|
||||
try {
|
||||
/** @var \Illuminate\Http\Client\Response $response */
|
||||
$response = Http::timeout(5)->get($this->apiUrl . '/health');
|
||||
return $response->successful();
|
||||
} catch (\Exception $e) {
|
||||
$pythonPath = 'python';
|
||||
$scriptPath = base_path('ml/predict.py');
|
||||
|
||||
if (!file_exists($scriptPath)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$testPayload = json_encode([
|
||||
'pekerjaan' => 'buruh',
|
||||
'penghasilan' => 500000,
|
||||
'jumlah_tanggungan' => 4,
|
||||
'aset_kepemilikan' => 'motor',
|
||||
'bantuan_lain' => 'tidak',
|
||||
'usia' => 50,
|
||||
]);
|
||||
|
||||
$descriptorspec = [
|
||||
0 => ['pipe', 'r'],
|
||||
1 => ['pipe', 'w'],
|
||||
2 => ['pipe', 'w'],
|
||||
];
|
||||
|
||||
$process = proc_open(
|
||||
$pythonPath . ' ' . escapeshellarg($scriptPath),
|
||||
$descriptorspec,
|
||||
$pipes,
|
||||
base_path()
|
||||
);
|
||||
|
||||
if (!is_resource($process)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
fwrite($pipes[0], $testPayload);
|
||||
fclose($pipes[0]);
|
||||
|
||||
$output = stream_get_contents($pipes[1]);
|
||||
fclose($pipes[1]);
|
||||
|
||||
fclose($pipes[2]);
|
||||
|
||||
$exitCode = proc_close($process);
|
||||
|
||||
if ($exitCode !== 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$result = json_decode($output, true);
|
||||
|
||||
return is_array($result) && isset($result['probability']) && isset($result['recommendation']);
|
||||
} catch (\Throwable $e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
|
@ -62,21 +151,12 @@ public function healthCheck()
|
|||
*/
|
||||
public function getBatchPredictions(array $dataArray)
|
||||
{
|
||||
try {
|
||||
/** @var \Illuminate\Http\Client\Response $response */
|
||||
$response = Http::timeout(60)->post($this->apiUrl . '/batch-predict', [
|
||||
'data' => $dataArray
|
||||
]);
|
||||
$results = [];
|
||||
|
||||
if ($response->successful()) {
|
||||
return $response->json();
|
||||
}
|
||||
|
||||
return null;
|
||||
|
||||
} catch (\Exception $e) {
|
||||
Log::error('ML Batch API Exception: ' . $e->getMessage());
|
||||
return null;
|
||||
foreach ($dataArray as $item) {
|
||||
$results[] = $this->getPrediction($item);
|
||||
}
|
||||
|
||||
return $results;
|
||||
}
|
||||
}
|
||||
|
|
@ -13,18 +13,33 @@ public function up(): void
|
|||
{
|
||||
Schema::create('penerima_finals', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->foreignId('calon_penerima_id')->constrained('calon_penerimas')->onDelete('cascade');
|
||||
|
||||
$table->foreignId('calon_penerima_id')
|
||||
->constrained('calon_penerimas')
|
||||
->onDelete('cascade');
|
||||
|
||||
$table->date('tanggal_penetapan');
|
||||
$table->string('periode_bantuan'); // Contoh: "2024 Triwulan 1"
|
||||
|
||||
$table->string('periode_bantuan'); // contoh: 2026 Triwulan 1
|
||||
|
||||
$table->decimal('jumlah_bantuan', 15, 2);
|
||||
$table->enum('status_pencairan', ['belum_cair', 'sudah_cair'])->default('belum_cair');
|
||||
|
||||
$table->enum('status_pencairan', [
|
||||
'belum_cair',
|
||||
'sudah_cair'
|
||||
])->default('belum_cair');
|
||||
|
||||
$table->date('tanggal_pencairan')->nullable();
|
||||
|
||||
$table->timestamps();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
Schema::dropIfExists('penerima_finals');
|
||||
}
|
||||
};
|
||||
};
|
||||
|
|
@ -0,0 +1,31 @@
|
|||
<?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->enum('tracking_status', [
|
||||
'draft',
|
||||
'terkirim',
|
||||
'sedang_validasi',
|
||||
'selesai'
|
||||
])->default('draft')->after('status_verifikasi');
|
||||
|
||||
});
|
||||
}
|
||||
|
||||
public function down(): void
|
||||
{
|
||||
Schema::table('calon_penerimas', function (Blueprint $table) {
|
||||
|
||||
$table->dropColumn('tracking_status');
|
||||
|
||||
});
|
||||
}
|
||||
};
|
||||
|
|
@ -0,0 +1,71 @@
|
|||
import json
|
||||
import sys
|
||||
|
||||
def predict(data):
|
||||
pekerjaan = str(data.get("pekerjaan","")).lower()
|
||||
penghasilan = float(data.get("penghasilan",0))
|
||||
tanggungan = int(data.get("jumlah_tanggungan",0))
|
||||
aset = str(data.get("aset_kepemilikan","")).lower()
|
||||
bantuan_lain = str(data.get("bantuan_lain","")).lower()
|
||||
usia = int(data.get("usia",0))
|
||||
|
||||
score = 0
|
||||
|
||||
if pekerjaan in ["buruh", "tidak bekerja", "petani", "nelayan"]:
|
||||
score += 0.2
|
||||
|
||||
if penghasilan < 1000000:
|
||||
score += 0.3
|
||||
elif penghasilan < 2000000:
|
||||
score += 0.2
|
||||
|
||||
if tanggungan >= 4:
|
||||
score += 0.2
|
||||
|
||||
if "mobil" in aset:
|
||||
score -= 0.2
|
||||
|
||||
if bantuan_lain == "ya":
|
||||
score -= 0.1
|
||||
|
||||
if usia >= 60:
|
||||
score += 0.1
|
||||
|
||||
probability = max(0, min(score,1))
|
||||
recommendation = "Layak" if probability >= 0.5 else "Tidak Layak"
|
||||
|
||||
return {
|
||||
"probability": round(probability,4),
|
||||
"recommendation": recommendation
|
||||
}
|
||||
|
||||
def read_input():
|
||||
"""
|
||||
Prioritas baca:
|
||||
1) STDIN (kalau ada)
|
||||
2) argv[1] (kalau ada)
|
||||
"""
|
||||
try:
|
||||
# Coba baca dari STDIN
|
||||
if not sys.stdin.isatty():
|
||||
raw = sys.stdin.read().strip()
|
||||
if raw:
|
||||
return json.loads(raw)
|
||||
except:
|
||||
pass
|
||||
|
||||
# Fallback ke argv
|
||||
if len(sys.argv) > 1:
|
||||
raw = sys.argv[1]
|
||||
return json.loads(raw)
|
||||
|
||||
raise ValueError("No JSON input provided")
|
||||
|
||||
if __name__ == "__main__":
|
||||
try:
|
||||
data = read_input()
|
||||
result = predict(data)
|
||||
print(json.dumps(result))
|
||||
except Exception as e:
|
||||
print(json.dumps({"error": str(e)}))
|
||||
sys.exit(1)
|
||||
File diff suppressed because it is too large
Load Diff
|
|
@ -4,7 +4,7 @@
|
|||
<div>
|
||||
<h2 class="font-semibold text-xl text-gray-800 leading-tight">Filterisasi</h2>
|
||||
<p class="text-sm text-gray-500 mt-0.5">
|
||||
Penetapan penerima BLT-DD berdasarkan probabilitas (hanya data yang sudah <b>disetujui</b>).
|
||||
Penetapan penerima BLT-DD berdasarkan probabilitas tertinggi per dusun.
|
||||
</p>
|
||||
</div>
|
||||
</x-slot>
|
||||
|
|
@ -16,6 +16,7 @@
|
|||
{{ session('success') }}
|
||||
</div>
|
||||
@endif
|
||||
|
||||
@if(session('error'))
|
||||
<div class="rounded-xl border border-red-200 bg-red-50 px-4 py-3 text-sm text-red-700">
|
||||
{{ session('error') }}
|
||||
|
|
@ -25,7 +26,7 @@
|
|||
<div class="bg-white rounded-2xl shadow-sm border border-gray-100 p-5">
|
||||
<div class="flex items-center gap-2 mb-4">
|
||||
<div class="w-1 h-4 bg-blue-600 rounded-full"></div>
|
||||
<h3 class="text-sm font-semibold text-gray-700">Pilih Dusun & Kuota (Disetujui saja)</h3>
|
||||
<h3 class="text-sm font-semibold text-gray-700">Pilih Dusun & Kuota</h3>
|
||||
</div>
|
||||
|
||||
<form method="GET" action="{{ route('admin.filterisasi') }}" class="grid grid-cols-1 md:grid-cols-4 gap-3">
|
||||
|
|
@ -69,7 +70,7 @@ class="px-4 py-2 text-sm font-semibold bg-gray-100 text-gray-700 rounded-xl hove
|
|||
<input type="hidden" name="dusun_id" value="{{ $dusunId }}">
|
||||
<input type="hidden" name="kuota" value="{{ (int)($kuota ?? 7) }}">
|
||||
<button type="submit"
|
||||
onclick="return confirm('Tetapkan {{ (int)($kuota ?? 7) }} orang teratas untuk dusun ini?')"
|
||||
onclick="return confirm('Tetapkan {{ (int)($kuota ?? 7) }} orang dengan probabilitas tertinggi untuk dusun ini?')"
|
||||
class="px-5 py-2 text-sm font-semibold bg-emerald-600 text-white rounded-xl hover:bg-emerald-700 transition">
|
||||
Tetapkan {{ (int)($kuota ?? 7) }} Teratas
|
||||
</button>
|
||||
|
|
@ -83,7 +84,7 @@ class="px-5 py-2 text-sm font-semibold bg-emerald-600 text-white rounded-xl hove
|
|||
<div class="px-5 py-3 border-b border-gray-100 bg-gray-50 flex items-center justify-between">
|
||||
<div class="flex items-center gap-2">
|
||||
<div class="w-1 h-4 bg-blue-600 rounded-full"></div>
|
||||
<span class="text-sm font-semibold text-gray-700">Kandidat Disetujui (urut probabilitas)</span>
|
||||
<span class="text-sm font-semibold text-gray-700">Kandidat Sedang Divalidasi / Sudah Diproses</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
|
@ -91,27 +92,52 @@ class="px-5 py-2 text-sm font-semibold bg-emerald-600 text-white rounded-xl hove
|
|||
<table class="w-full text-sm">
|
||||
<thead>
|
||||
<tr class="bg-gray-50 border-b border-gray-100">
|
||||
<th class="px-4 py-3 text-left text-xs font-semibold text-gray-400 uppercase">Ranking</th>
|
||||
<th class="px-4 py-3 text-left text-xs font-semibold text-gray-400 uppercase">Nama</th>
|
||||
<th class="px-4 py-3 text-left text-xs font-semibold text-gray-400 uppercase">NIK</th>
|
||||
<th class="px-4 py-3 text-left text-xs font-semibold text-gray-400 uppercase">Dusun</th>
|
||||
<th class="px-4 py-3 text-left text-xs font-semibold text-gray-400 uppercase">Prob</th>
|
||||
<th class="px-4 py-3 text-left text-xs font-semibold text-gray-400 uppercase">Probabilitas</th>
|
||||
<th class="px-4 py-3 text-left text-xs font-semibold text-gray-400 uppercase">Status</th>
|
||||
<th class="px-4 py-3 text-left text-xs font-semibold text-gray-400 uppercase">Final</th>
|
||||
</tr>
|
||||
</thead>
|
||||
|
||||
<tbody class="divide-y divide-gray-50">
|
||||
@forelse(($candidates ?? []) as $w)
|
||||
@forelse(($candidates ?? []) as $index => $w)
|
||||
@php
|
||||
$prob = optional($w->prediksiKelayakan)->probability;
|
||||
$prob = $w->prob ?? optional($w->prediksiKelayakan)->probability;
|
||||
$probPct = $prob !== null ? ($prob <= 1 ? $prob * 100 : $prob) : null;
|
||||
$picked = isset($pickedIds) ? $pickedIds->contains($w->id) : false;
|
||||
@endphp
|
||||
<tr class="hover:bg-blue-50/20 {{ $picked ? 'bg-emerald-50/40' : '' }}">
|
||||
<td class="px-4 py-3 font-semibold text-gray-800">{{ $w->nama_lengkap }}</td>
|
||||
<td class="px-4 py-3 text-gray-600">{{ $w->nik }}</td>
|
||||
<td class="px-4 py-3 text-gray-600">{{ optional(optional($w->rt)->dusun)->nama_dusun ?? '-' }}</td>
|
||||
<td class="px-4 py-3 text-gray-700">
|
||||
{{ $prob !== null ? number_format((float)$prob, 4) : '-' }}
|
||||
<td class="px-4 py-3 text-sm font-semibold text-gray-700">
|
||||
{{ (($candidates->currentPage() - 1) * $candidates->perPage()) + $index + 1 }}
|
||||
</td>
|
||||
|
||||
<td class="px-4 py-3 font-semibold text-gray-800">{{ $w->nama_lengkap }}</td>
|
||||
|
||||
<td class="px-4 py-3 text-gray-600">{{ $w->nik }}</td>
|
||||
|
||||
<td class="px-4 py-3 text-gray-600">{{ optional(optional($w->rt)->dusun)->nama_dusun ?? '-' }}</td>
|
||||
|
||||
<td class="px-4 py-3 text-gray-700">
|
||||
@if($probPct !== null)
|
||||
<span class="font-semibold">{{ number_format((float)$probPct, 1) }}%</span>
|
||||
@else
|
||||
-
|
||||
@endif
|
||||
</td>
|
||||
|
||||
<td class="px-4 py-3">
|
||||
@if(($w->status_verifikasi ?? '') === 'pending')
|
||||
<span class="px-2 py-1 rounded-full text-xs bg-amber-100 text-amber-700">Pending</span>
|
||||
@elseif(($w->status_verifikasi ?? '') === 'disetujui')
|
||||
<span class="px-2 py-1 rounded-full text-xs bg-emerald-100 text-emerald-700">Disetujui</span>
|
||||
@else
|
||||
<span class="px-2 py-1 rounded-full text-xs bg-rose-100 text-rose-700">Ditolak</span>
|
||||
@endif
|
||||
</td>
|
||||
|
||||
<td class="px-4 py-3">
|
||||
@if($picked)
|
||||
<span class="px-2 py-1 rounded-full text-xs bg-emerald-100 text-emerald-700">Terpilih</span>
|
||||
|
|
@ -122,8 +148,8 @@ class="px-5 py-2 text-sm font-semibold bg-emerald-600 text-white rounded-xl hove
|
|||
</tr>
|
||||
@empty
|
||||
<tr>
|
||||
<td colspan="5" class="px-4 py-12 text-center text-sm text-gray-400">
|
||||
Pilih dusun dulu untuk menampilkan kandidat.
|
||||
<td colspan="7" class="px-4 py-12 text-center text-sm text-gray-400">
|
||||
Pilih dusun dulu untuk menampilkan kandidat filterisasi.
|
||||
</td>
|
||||
</tr>
|
||||
@endforelse
|
||||
|
|
@ -138,8 +164,14 @@ class="px-5 py-2 text-sm font-semibold bg-emerald-600 text-white rounded-xl hove
|
|||
@endif
|
||||
</div>
|
||||
|
||||
<div class="bg-amber-50 border border-amber-100 rounded-2xl px-5 py-4 text-xs text-amber-700">
|
||||
<b>Catatan:</b> Kandidat yang ditampilkan hanya yang <b>status_verifikasi = disetujui</b>. Kuota default 7 per dusun.
|
||||
<div class="bg-amber-50 border border-amber-100 rounded-2xl px-5 py-4 text-xs text-amber-700 leading-6">
|
||||
<b>Catatan:</b>
|
||||
Kandidat yang tampil di halaman ini adalah data warga yang berada pada tahap
|
||||
<b>sedang_validasi</b> atau <b>selesai</b>.
|
||||
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>
|
||||
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -3,79 +3,152 @@
|
|||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Register - Sistem BLT-DD</title>
|
||||
<title>Daftar Akun — SiBantuDes</title>
|
||||
@vite(['resources/css/app.css', 'resources/js/app.js'])
|
||||
<style>
|
||||
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
|
||||
body {
|
||||
min-height: 100vh;
|
||||
font-family: ui-sans-serif, system-ui, -apple-system, 'Segoe UI', sans-serif;
|
||||
background: linear-gradient(145deg, #eff6ff 0%, #f8fafc 45%, #f0f9ff 100%);
|
||||
display: flex; align-items: center; justify-content: center;
|
||||
padding: 12px 16px; /* ← dari 24px */
|
||||
}
|
||||
.card { width:100%; max-width:460px; background:#fff; border-radius:24px; overflow:hidden; box-shadow:0 2px 4px rgba(0,0,0,0.04),0 8px 24px rgba(37,99,235,0.08),0 24px 48px rgba(37,99,235,0.06); }
|
||||
.hero { position:relative; padding:20px 28px 18px; background:linear-gradient(135deg,#1e3a8a 0%,#1d4ed8 55%,#3b82f6 100%); overflow:hidden; } /* ← dari 30px 32px 28px */
|
||||
.hero-blob-1 { position:absolute; top:-32px; right:-32px; width:130px; height:130px; background:rgba(255,255,255,.09); border-radius:50%; }
|
||||
.hero-blob-2 { position:absolute; bottom:-40px; left:-16px; width:100px; height:100px; background:rgba(255,255,255,.07); border-radius:50%; }
|
||||
.hero-blob-3 { position:absolute; top:10px; right:90px; width:40px; height:40px; background:rgba(255,255,255,.10); border-radius:50%; }
|
||||
.hero-inner { position:relative; z-index:1; display:flex; align-items:center; gap:14px; }
|
||||
.hero-logo { width:48px; height:48px; flex-shrink:0; background:rgba(255,255,255,.16); border:2px solid rgba(255,255,255,.22); border-radius:14px; display:flex; align-items:center; justify-content:center; } /* ← dari 56px */
|
||||
.hero-logo svg { width:24px; height:24px; stroke:#fff; }
|
||||
.hero-app { font-size:10px; font-weight:700; letter-spacing:.14em; text-transform:uppercase; color:rgba(191,219,254,.85); margin-bottom:2px; }
|
||||
.hero-title { font-size:19px; font-weight:800; color:#fff; line-height:1.2; } /* ← dari 21px */
|
||||
.hero-sub { font-size:12px; color:rgba(191,219,254,.8); margin-top:1px; }
|
||||
.body { padding:18px 28px 14px; } /* ← dari 28px 32px 24px */
|
||||
.error-box { display:flex; gap:10px; align-items:flex-start; background:#fef2f2; border:1.5px solid #fecaca; border-radius:12px; padding:10px 12px; margin-bottom:14px; }
|
||||
.error-box svg { width:15px; height:15px; stroke:#ef4444; flex-shrink:0; margin-top:1px; }
|
||||
.error-box ul { list-style:none; }
|
||||
.error-box li { font-size:12px; color:#dc2626; line-height:1.5; }
|
||||
.field { margin-bottom:11px; } /* ← dari 16px */
|
||||
.field-label { display:block; margin-bottom:5px; font-size:10.5px; font-weight:700; text-transform:uppercase; letter-spacing:.08em; color:#6b7280; }
|
||||
.field-label .req { color:#ef4444; margin-left:2px; }
|
||||
.field-hint { font-size:11px; color:#9ca3af; margin-top:4px; padding-left:2px; }
|
||||
.iw { position:relative; display:flex; align-items:center; }
|
||||
.iw-icon { position:absolute; left:10px; z-index:1; pointer-events:none; width:30px; height:30px; border-radius:8px; display:flex; align-items:center; justify-content:center; }
|
||||
.iw-icon svg { width:14px; height:14px; fill:none; }
|
||||
.iw-icon.blue { background:#eff6ff; } .iw-icon.blue svg { stroke:#3b82f6; }
|
||||
.iw-icon.green { background:#f0fdf4; } .iw-icon.green svg { stroke:#22c55e; }
|
||||
.iw-icon.amber { background:#fffbeb; } .iw-icon.amber svg { stroke:#f59e0b; }
|
||||
.iw input, .iw select { width:100%; padding:10px 14px 10px 50px; font-size:13.5px; color:#111827; background:#f9fafb; border:1.5px solid #e5e7eb; border-radius:12px; outline:none; transition:border-color .15s,background .15s,box-shadow .15s; -webkit-appearance:none; appearance:none; } /* ← padding dari 13px */
|
||||
.iw input::placeholder { color:#9ca3af; font-size:13px; }
|
||||
.iw input:focus, .iw select:focus { border-color:#3b82f6; background:#fff; box-shadow:0 0 0 3px rgba(59,130,246,.12); }
|
||||
.iw input.err { border-color:#fca5a5; background:#fef2f2; }
|
||||
.iw-arrow { position:absolute; right:12px; pointer-events:none; }
|
||||
.iw-arrow svg { width:14px; height:14px; stroke:#9ca3af; fill:none; }
|
||||
.iw-toggle { position:absolute; right:9px; width:28px; height:28px; border-radius:7px; background:none; border:none; cursor:pointer; display:flex; align-items:center; justify-content:center; color:#9ca3af; transition:color .15s,background .15s; }
|
||||
.iw-toggle:hover { color:#6b7280; background:#f3f4f6; }
|
||||
.iw-toggle svg { width:14px; height:14px; stroke:currentColor; fill:none; }
|
||||
.divider { display:flex; align-items:center; gap:10px; margin:4px 0 12px; } /* ← dari 6px 0 18px */
|
||||
.divider::before, .divider::after { content:''; flex:1; height:1px; background:#f1f5f9; }
|
||||
.divider span { font-size:10.5px; font-weight:600; color:#cbd5e1; white-space:nowrap; }
|
||||
.btn-submit { width:100%; margin-top:8px; padding:12px; border:none; border-radius:12px; font-size:14px; font-weight:700; color:#fff; cursor:pointer; display:flex; align-items:center; justify-content:center; gap:8px; background:linear-gradient(135deg,#2563eb 0%,#1d4ed8 100%); box-shadow:0 4px 16px rgba(37,99,235,.32); transition:box-shadow .2s,transform .1s; } /* ← padding dari 14px */
|
||||
.btn-submit:hover { box-shadow:0 6px 24px rgba(37,99,235,.44); }
|
||||
.btn-submit:active { transform:scale(.98); }
|
||||
.btn-submit svg { width:16px; height:16px; stroke:#fff; fill:none; flex-shrink:0; }
|
||||
.login-link { margin-top:14px; padding-top:12px; border-top:1px solid #f1f5f9; text-align:center; font-size:13px; color:#9ca3af; } /* ← dari 20px/18px */
|
||||
.login-link a { color:#2563eb; font-weight:600; text-decoration:none; margin-left:2px; }
|
||||
.login-link a:hover { text-decoration:underline; }
|
||||
.footer { padding:9px 28px; background:#f9fafb; border-top:1px solid #f1f5f9; display:flex; align-items:center; justify-content:center; gap:6px; } /* ← dari 11px */
|
||||
.footer svg { width:12px; height:12px; fill:#d1d5db; }
|
||||
.footer span { font-size:11px; color:#d1d5db; font-weight:500; }
|
||||
</style>
|
||||
</head>
|
||||
<body class="bg-gradient-to-br from-primary-50 via-white to-blue-50">
|
||||
<div class="min-h-screen flex items-center justify-center p-4">
|
||||
<div class="max-w-md w-full bg-white rounded-2xl shadow-2xl p-8">
|
||||
<div class="mb-8">
|
||||
<div class="flex items-center justify-center mb-4">
|
||||
<div class="w-16 h-16 bg-primary-600 rounded-xl flex items-center justify-center">
|
||||
<svg class="w-10 h-10 text-white" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M3 12l2-2m0 0l7-7 7 7M5 10v10a1 1 0 001 1h3m10-11l2 2m-2-2v10a1 1 0 01-1 1h-3m-6 0a1 1 0 001-1v-4a1 1 0 011-1h2a1 1 0 011 1v4a1 1 0 001 1m-6 0h6"></path>
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
<h2 class="text-3xl font-bold text-gray-800 text-center mb-2">Daftar Akun RT</h2>
|
||||
<p class="text-gray-600 text-center">Lengkapi data untuk mendaftar</p>
|
||||
<body>
|
||||
<div class="card">
|
||||
<div class="hero">
|
||||
<div class="hero-blob-1"></div><div class="hero-blob-2"></div><div class="hero-blob-3"></div>
|
||||
<div class="hero-inner">
|
||||
<div class="hero-logo">
|
||||
<svg fill="none" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M3 12l2-2m0 0l7-7 7 7M5 10v10a1 1 0 001 1h3m10-11l2 2m-2-2v10a1 1 0 01-1 1h-3m-6 0a1 1 0 001-1v-4a1 1 0 011-1h2a1 1 0 011 1v4a1 1 0 001 1m-6 0h6"/></svg>
|
||||
</div>
|
||||
|
||||
@if($errors->any())
|
||||
<div class="mb-6 bg-red-50 border-l-4 border-red-500 p-4 rounded-lg">
|
||||
<ul class="text-red-700 text-sm space-y-1">
|
||||
@foreach($errors->all() as $error)
|
||||
<li>{{ $error }}</li>
|
||||
@endforeach
|
||||
</ul>
|
||||
</div>
|
||||
@endif
|
||||
|
||||
<form action="{{ route('register') }}" method="POST" class="space-y-5">
|
||||
@csrf
|
||||
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-gray-700 mb-2">Nama Lengkap</label>
|
||||
<input type="text" name="name" value="{{ old('name') }}" required
|
||||
class="input-field" placeholder="Masukkan nama lengkap">
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-gray-700 mb-2">Email</label>
|
||||
<input type="email" name="email" value="{{ old('email') }}" required
|
||||
class="input-field" placeholder="nama@email.com">
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-gray-700 mb-2">Nama Dusun</label>
|
||||
<input type="text" name="dusun" value="{{ old('dusun') }}" required
|
||||
class="input-field" placeholder="Contoh: Ngronggo">
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-gray-700 mb-2">Password</label>
|
||||
<input type="password" name="password" required
|
||||
class="input-field" placeholder="Minimal 6 karakter">
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-gray-700 mb-2">Konfirmasi Password</label>
|
||||
<input type="password" name="password_confirmation" required
|
||||
class="input-field" placeholder="Ulangi password">
|
||||
</div>
|
||||
|
||||
<button type="submit" class="w-full btn-primary py-3 text-lg font-semibold mt-6">
|
||||
Daftar Sekarang
|
||||
</button>
|
||||
</form>
|
||||
|
||||
<div class="mt-6 text-center">
|
||||
<p class="text-sm text-gray-600">
|
||||
Sudah punya akun?
|
||||
<a href="{{ route('login') }}" class="text-primary-600 hover:text-primary-700 font-medium">Masuk di sini</a>
|
||||
</p>
|
||||
<div>
|
||||
<p class="hero-app">SiBantuDes</p>
|
||||
<h1 class="hero-title">Daftar Akun RT</h1>
|
||||
<p class="hero-sub">Lengkapi data untuk mendaftar ke sistem</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="body">
|
||||
@if($errors->any())
|
||||
<div class="error-box">
|
||||
<svg fill="none" 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>
|
||||
<ul>@foreach($errors->all() as $e)<li>{{ $e }}</li>@endforeach</ul>
|
||||
</div>
|
||||
@endif
|
||||
<form action="{{ route('register') }}" method="POST">
|
||||
@csrf
|
||||
<div class="field">
|
||||
<label class="field-label">Nama Lengkap <span class="req">*</span></label>
|
||||
<div class="iw">
|
||||
<div class="iw-icon blue"><svg fill="none" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M16 7a4 4 0 11-8 0 4 4 0 018 0zM12 14a7 7 0 00-7 7h14a7 7 0 00-7-7z"/></svg></div>
|
||||
<input type="text" name="name" value="{{ old('name') }}" required placeholder="Masukkan nama lengkap" class="{{ $errors->has('name') ? 'err' : '' }}">
|
||||
</div>
|
||||
</div>
|
||||
<div class="field">
|
||||
<label class="field-label">Email <span class="req">*</span></label>
|
||||
<div class="iw">
|
||||
<div class="iw-icon blue"><svg fill="none" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M3 8l7.89 5.26a2 2 0 002.22 0L21 8M5 19h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v10a2 2 0 002 2z"/></svg></div>
|
||||
<input type="email" name="email" value="{{ old('email') }}" required placeholder="nama@email.com" class="{{ $errors->has('email') ? 'err' : '' }}">
|
||||
</div>
|
||||
</div>
|
||||
<div class="field">
|
||||
<label class="field-label">Pilih RT <span class="req">*</span></label>
|
||||
<div class="iw">
|
||||
<div class="iw-icon green"><svg fill="none" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" 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></div>
|
||||
<select name="rt_id" required style="padding-right:40px;" class="{{ $errors->has('rt_id') ? 'err' : '' }}">
|
||||
<option value="">— Pilih RT —</option>
|
||||
@forelse(($rts ?? []) as $rt)
|
||||
<option value="{{ $rt->id }}" {{ (string)old('rt_id') === (string)$rt->id ? 'selected' : '' }}>RT {{ str_pad($rt->nomor_rt, 3, '0', STR_PAD_LEFT) }} — {{ $rt->dusun->nama_dusun ?? '-' }}</option>
|
||||
@empty
|
||||
<option value="" disabled>Data RT belum tersedia</option>
|
||||
@endforelse
|
||||
</select>
|
||||
<div class="iw-arrow"><svg fill="none" 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>
|
||||
<p class="field-hint">Dusun mengikuti RT yang dipilih secara otomatis.</p>
|
||||
</div>
|
||||
<div class="divider"><span>Keamanan Akun</span></div>
|
||||
<div class="field">
|
||||
<label class="field-label">Password <span class="req">*</span></label>
|
||||
<div class="iw">
|
||||
<div class="iw-icon amber"><svg fill="none" 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></div>
|
||||
<input type="password" name="password" id="pwd" required placeholder="Minimal 6 karakter" style="padding-right:46px;" class="{{ $errors->has('password') ? 'err' : '' }}">
|
||||
<button type="button" class="iw-toggle" onclick="togglePwd('pwd',this)"><svg fill="none" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13.875 18.825A10.05 10.05 0 0112 19c-4.478 0-8.268-2.943-9.543-7a9.97 9.97 0 011.563-3.029m5.858.908a3 3 0 114.243 4.243M9.878 9.878l4.242 4.242M9.88 9.88l-3.29-3.29m7.532 7.532l3.29 3.29M3 3l3.59 3.59m0 0A9.953 9.953 0 0112 5c4.478 0 8.268 2.943 9.543 7a10.025 10.025 0 01-4.132 5.411m0 0L21 21"/></svg></button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="field">
|
||||
<label class="field-label">Konfirmasi Password <span class="req">*</span></label>
|
||||
<div class="iw">
|
||||
<div class="iw-icon amber"><svg fill="none" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12l2 2 4-4m5.618-4.016A11.955 11.955 0 0112 2.944a11.955 11.955 0 01-8.618 3.04A12.02 12.02 0 003 9c0 5.591 3.824 10.29 9 11.622 5.176-1.332 9-6.03 9-11.622 0-1.042-.133-2.052-.382-3.016z"/></svg></div>
|
||||
<input type="password" name="password_confirmation" id="pwd2" required placeholder="Ulangi password" style="padding-right:46px;">
|
||||
<button type="button" class="iw-toggle" onclick="togglePwd('pwd2',this)"><svg fill="none" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13.875 18.825A10.05 10.05 0 0112 19c-4.478 0-8.268-2.943-9.543-7a9.97 9.97 0 011.563-3.029m5.858.908a3 3 0 114.243 4.243M9.878 9.878l4.242 4.242M9.88 9.88l-3.29-3.29m7.532 7.532l3.29 3.29M3 3l3.59 3.59m0 0A9.953 9.953 0 0112 5c4.478 0 8.268 2.943 9.543 7a10.025 10.025 0 01-4.132 5.411m0 0L21 21"/></svg></button>
|
||||
</div>
|
||||
</div>
|
||||
<button type="submit" class="btn-submit">
|
||||
<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>
|
||||
Daftar Sekarang
|
||||
</button>
|
||||
</form>
|
||||
<div class="login-link">Sudah punya akun? <a href="{{ route('login') }}">Masuk di sini →</a></div>
|
||||
</div>
|
||||
<div class="footer">
|
||||
<svg viewBox="0 0 20 20"><path fill-rule="evenodd" d="M4 4a2 2 0 012-2h8a2 2 0 012 2v12a1 1 0 110 2h-3a1 1 0 01-1-1v-2a1 1 0 00-1-1H9a1 1 0 00-1 1v2a1 1 0 01-1 1H4a1 1 0 110-2V4zm3 1h2v2H7V5zm2 4H7v2h2V9zm2-4h2v2h-2V5zm2 4h-2v2h2V9z" clip-rule="evenodd"/></svg>
|
||||
<span>SiBantuDes · Desa Ngerong</span>
|
||||
</div>
|
||||
</div>
|
||||
<script>
|
||||
function togglePwd(id,btn){const i=document.getElementById(id);const s=i.type==='password';i.type=s?'text':'password';btn.querySelector('svg').innerHTML=s?'<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"/>':'<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13.875 18.825A10.05 10.05 0 0112 19c-4.478 0-8.268-2.943-9.543-7a9.97 9.97 0 011.563-3.029m5.858.908a3 3 0 114.243 4.243M9.878 9.878l4.242 4.242M9.88 9.88l-3.29-3.29m7.532 7.532l3.29 3.29M3 3l3.59 3.59m0 0A9.953 9.953 0 0112 5c4.478 0 8.268 2.943 9.543 7a10.025 10.025 0 01-4.132 5.411m0 0L21 21"/>';}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
|
@ -6,31 +6,31 @@
|
|||
</div>
|
||||
</x-slot>
|
||||
|
||||
{{-- TAMPILKAN ERROR VALIDASI --}}
|
||||
@if ($errors->any())
|
||||
<div class="mb-4 rounded-xl border border-red-200 bg-red-50 p-4 text-sm text-red-700">
|
||||
<div class="font-semibold mb-2">Gagal menyimpan:</div>
|
||||
<ul class="list-disc ps-5 space-y-1">
|
||||
@foreach ($errors->all() as $error)
|
||||
<li>{{ $error }}</li>
|
||||
@endforeach
|
||||
</ul>
|
||||
</div>
|
||||
@endif
|
||||
{{-- TAMPILKAN ERROR VALIDASI --}}
|
||||
@if ($errors->any())
|
||||
<div class="mb-4 rounded-xl border border-red-200 bg-red-50 p-4 text-sm text-red-700">
|
||||
<div class="font-semibold mb-2">Gagal menyimpan:</div>
|
||||
<ul class="list-disc ps-5 space-y-1">
|
||||
@foreach ($errors->all() as $error)
|
||||
<li>{{ $error }}</li>
|
||||
@endforeach
|
||||
</ul>
|
||||
</div>
|
||||
@endif
|
||||
|
||||
{{-- TAMPILKAN ERROR DARI TRY-CATCH CONTROLLER --}}
|
||||
@if (session('error'))
|
||||
<div class="mb-4 rounded-xl border border-red-200 bg-red-50 p-4 text-sm text-red-700">
|
||||
{{ session('error') }}
|
||||
</div>
|
||||
@endif
|
||||
{{-- TAMPILKAN ERROR DARI TRY-CATCH CONTROLLER --}}
|
||||
@if (session('error'))
|
||||
<div class="mb-4 rounded-xl border border-red-200 bg-red-50 p-4 text-sm text-red-700">
|
||||
{{ session('error') }}
|
||||
</div>
|
||||
@endif
|
||||
|
||||
{{-- TAMPILKAN SUCCESS --}}
|
||||
@if (session('success'))
|
||||
<div class="mb-4 rounded-xl border border-green-200 bg-green-50 p-4 text-sm text-green-700">
|
||||
{{ session('success') }}
|
||||
</div>
|
||||
@endif
|
||||
{{-- TAMPILKAN SUCCESS --}}
|
||||
@if (session('success'))
|
||||
<div class="mb-4 rounded-xl border border-green-200 bg-green-50 p-4 text-sm text-green-700">
|
||||
{{ session('success') }}
|
||||
</div>
|
||||
@endif
|
||||
|
||||
<form action="{{ route('rt.calon-penerima.store') }}" method="POST" id="formPendataan" novalidate>
|
||||
@csrf
|
||||
|
|
@ -41,60 +41,98 @@
|
|||
<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="p-5 grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-4">
|
||||
|
||||
{{-- RT --}}
|
||||
<div class="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
|
||||
|
||||
<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 name="rt_id" id="rt_id" 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 RT --</option>
|
||||
@if(isset($rts) && $rts->count() > 0)
|
||||
@foreach($rts as $rt)
|
||||
<option value="{{ $rt->id }}">RT {{ str_pad($rt->nomor_rt, 3, '0', STR_PAD_LEFT) }} - {{ $rt->dusun->nama_dusun ?? '' }}</option>
|
||||
@endforeach
|
||||
@else
|
||||
<option value="1">RT 001</option>
|
||||
<option value="2">RT 002</option>
|
||||
<option value="3">RT 003</option>
|
||||
<option value="4">RT 004</option>
|
||||
<option value="5">RT 005</option>
|
||||
<option value="6">RT 006</option>
|
||||
@endif
|
||||
<label class="block text-xs font-semibold text-gray-600 mb-1">
|
||||
RT <span class="text-red-500">*</span>
|
||||
</label>
|
||||
|
||||
{{-- tampilkan tapi terkunci --}}
|
||||
<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
|
||||
>
|
||||
@foreach(($rts ?? []) as $rt)
|
||||
<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
|
||||
</select>
|
||||
|
||||
{{-- yang dikirim --}}
|
||||
<input type="hidden" name="rt_id" id="rt_id" value="{{ $myRtId }}">
|
||||
|
||||
<p class="error-msg text-xs text-red-500 mt-1 hidden">RT wajib dipilih.</p>
|
||||
</div>
|
||||
|
||||
{{-- NO KK --}}
|
||||
<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>
|
||||
<input type="text" name="no_kk" id="no_kk" maxlength="16"
|
||||
<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>
|
||||
placeholder="16 digit No. KK"
|
||||
required
|
||||
>
|
||||
<p class="error-msg text-xs text-red-500 mt-1 hidden">No. KK harus 16 digit angka.</p>
|
||||
</div>
|
||||
|
||||
{{-- NIK --}}
|
||||
<div class="field-group">
|
||||
<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"
|
||||
<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>
|
||||
placeholder="16 digit NIK"
|
||||
required
|
||||
>
|
||||
<p class="error-msg text-xs text-red-500 mt-1 hidden">NIK harus tepat 16 digit angka.</p>
|
||||
</div>
|
||||
|
||||
{{-- NAMA --}}
|
||||
<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>
|
||||
<input type="text" name="nama_lengkap" id="nama_lengkap"
|
||||
<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>
|
||||
placeholder="Nama sesuai KTP"
|
||||
required
|
||||
>
|
||||
<p class="error-msg text-xs text-red-500 mt-1 hidden">Nama lengkap wajib diisi.</p>
|
||||
</div>
|
||||
|
||||
{{-- JENIS KELAMIN --}}
|
||||
<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" 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">
|
||||
<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"
|
||||
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="Laki-laki">Laki-laki</option>
|
||||
<option value="Perempuan">Perempuan</option>
|
||||
</select>
|
||||
|
|
@ -102,35 +140,63 @@ class="input-field w-full border border-gray-200 rounded-xl px-3 py-2 text-sm fo
|
|||
|
||||
{{-- TEMPAT LAHIR --}}
|
||||
<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>
|
||||
<input type="text" name="tempat_lahir" id="tempat_lahir"
|
||||
<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>
|
||||
placeholder="Kota/Kabupaten"
|
||||
required
|
||||
>
|
||||
<p class="error-msg text-xs text-red-500 mt-1 hidden">Tempat lahir wajib diisi.</p>
|
||||
</div>
|
||||
|
||||
{{-- TANGGAL LAHIR --}}
|
||||
<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>
|
||||
<input type="date" name="tanggal_lahir" id="tanggal_lahir"
|
||||
<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>
|
||||
required
|
||||
>
|
||||
<p class="error-msg text-xs text-red-500 mt-1 hidden">Tanggal lahir wajib diisi.</p>
|
||||
</div>
|
||||
|
||||
{{-- USIA --}}
|
||||
<div class="field-group">
|
||||
<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"
|
||||
<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>
|
||||
placeholder="Otomatis dari tanggal lahir"
|
||||
required
|
||||
>
|
||||
<p class="error-msg text-xs text-red-500 mt-1 hidden">Usia harus antara 0–150 tahun.</p>
|
||||
</div>
|
||||
|
||||
{{-- STATUS PERKAWINAN --}}
|
||||
<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>
|
||||
<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">
|
||||
<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>
|
||||
<option value="Belum Kawin">Belum Kawin</option>
|
||||
<option value="Kawin">Kawin</option>
|
||||
|
|
@ -139,7 +205,6 @@ class="input-field w-full border border-gray-200 rounded-xl px-3 py-2 text-sm fo
|
|||
</select>
|
||||
<p class="error-msg text-xs text-red-500 mt-1 hidden">Status perkawinan wajib dipilih.</p>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
|
@ -149,35 +214,63 @@ 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="p-5 grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-4">
|
||||
|
||||
<div class="p-5 grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-4">
|
||||
{{-- ALAMAT --}}
|
||||
<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>
|
||||
<input type="text" name="alamat" id="alamat"
|
||||
<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>
|
||||
placeholder="Jalan, nomor rumah, RT/RW"
|
||||
required
|
||||
>
|
||||
<p class="error-msg text-xs text-red-500 mt-1 hidden">Alamat wajib diisi.</p>
|
||||
</div>
|
||||
|
||||
{{-- DUSUN --}}
|
||||
@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>
|
||||
<input type="text" name="desa" id="desa"
|
||||
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 dusun" required>
|
||||
<label class="block text-xs font-semibold text-gray-600 mb-1">
|
||||
Dusun <span class="text-red-500">*</span>
|
||||
</label>
|
||||
|
||||
{{-- tampilkan tapi terkunci --}}
|
||||
<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"
|
||||
value="{{ $myDusunName }}"
|
||||
readonly
|
||||
>
|
||||
|
||||
{{-- yang dikirim --}}
|
||||
<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>
|
||||
|
||||
{{-- ASET --}}
|
||||
<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"
|
||||
<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>
|
||||
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>
|
||||
|
||||
|
|
@ -187,59 +280,90 @@ 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="p-5 grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-4">
|
||||
|
||||
<div class="p-5 grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-4">
|
||||
{{-- PEKERJAAN --}}
|
||||
<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"
|
||||
<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>
|
||||
placeholder="Jenis pekerjaan"
|
||||
required
|
||||
>
|
||||
<p class="error-msg text-xs text-red-500 mt-1 hidden">Pekerjaan wajib diisi.</p>
|
||||
</div>
|
||||
|
||||
{{-- PENGHASILAN --}}
|
||||
<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="number"
|
||||
step="0.01"
|
||||
name="penghasilan"
|
||||
id="penghasilan"
|
||||
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="0" required>
|
||||
placeholder="0"
|
||||
required
|
||||
>
|
||||
<p class="error-msg text-xs text-red-500 mt-1 hidden">Penghasilan tidak boleh negatif.</p>
|
||||
</div>
|
||||
|
||||
{{-- JUMLAH TANGGUNGAN --}}
|
||||
<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>
|
||||
<input type="number" name="jumlah_tanggungan" id="jumlah_tanggungan" min="0"
|
||||
<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>
|
||||
placeholder="Jumlah orang"
|
||||
required
|
||||
>
|
||||
<p class="error-msg text-xs text-red-500 mt-1 hidden">Jumlah tanggungan tidak boleh negatif.</p>
|
||||
</div>
|
||||
|
||||
{{-- BANTUAN LAIN --}}
|
||||
<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" 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">
|
||||
<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"
|
||||
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>
|
||||
</div>
|
||||
|
||||
{{-- ACTIONS --}}
|
||||
<div class="flex justify-end gap-3">
|
||||
<a href="{{ route('rt.calon-penerima.index') }}"
|
||||
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">
|
||||
<a
|
||||
href="{{ route('rt.calon-penerima.index') }}"
|
||||
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"
|
||||
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">
|
||||
<button
|
||||
type="submit"
|
||||
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
|
||||
</button>
|
||||
</div>
|
||||
|
||||
</form>
|
||||
|
||||
{{-- TOAST NOTIFIKASI --}}
|
||||
|
|
@ -338,5 +462,4 @@ function showToast(msg) {
|
|||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
</x-app-layout>
|
||||
|
|
@ -5,7 +5,7 @@
|
|||
<h2 class="font-semibold text-xl text-gray-800 leading-tight">Edit Pendataan Warga</h2>
|
||||
<p class="text-sm text-gray-500 mt-0.5">Perbarui data dengan benar sebelum menyimpan.</p>
|
||||
</div>
|
||||
<a href="{{ route('calon-penerima.index') }}"
|
||||
<a href="{{ route('rt.calon-penerima.index') }}"
|
||||
class="inline-flex items-center gap-1.5 px-4 py-2 bg-white border border-gray-200 text-gray-600 text-sm font-medium 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="M10 19l-7-7m0 0l7-7m-7 7h18"/>
|
||||
|
|
@ -32,10 +32,13 @@ class="inline-flex items-center gap-1.5 px-4 py-2 bg-white border border-gray-20
|
|||
</div>
|
||||
@endif
|
||||
|
||||
<form action="{{ route('calon-penerima.update', $calonPenerima->id) }}" method="POST" id="formEditPendataan" novalidate>
|
||||
<form action="{{ route('rt.calon-penerima.update', $calonPenerima->id) }}" method="POST" id="formEditPendataan" novalidate>
|
||||
@csrf
|
||||
@method('PUT')
|
||||
|
||||
{{-- hidden aman, walau nanti tetap di-override controller --}}
|
||||
<input type="hidden" name="rt_id" value="{{ $calonPenerima->rt_id }}">
|
||||
|
||||
{{-- SECTION 1: Data Identitas --}}
|
||||
<div class="bg-white rounded-2xl shadow-sm border border-gray-100 mb-4 overflow-hidden">
|
||||
<div class="px-5 py-3 border-b border-gray-100 flex items-center gap-2 bg-gray-50">
|
||||
|
|
@ -44,19 +47,14 @@ class="inline-flex items-center gap-1.5 px-4 py-2 bg-white border border-gray-20
|
|||
</div>
|
||||
<div class="p-5 grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-4">
|
||||
|
||||
{{-- RT --}}
|
||||
{{-- RT (readonly) --}}
|
||||
<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 name="rt_id" id="rt_id"
|
||||
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 RT --</option>
|
||||
@foreach($rts as $rt)
|
||||
<option value="{{ $rt->id }}" {{ (string)old('rt_id', $calonPenerima->rt_id) === (string)$rt->id ? 'selected' : '' }}>
|
||||
RT {{ str_pad($rt->nomor_rt, 3, '0', STR_PAD_LEFT) }} - {{ $rt->dusun->nama_dusun ?? '' }}
|
||||
</option>
|
||||
@endforeach
|
||||
</select>
|
||||
<p class="error-msg text-xs text-red-500 mt-1 hidden">RT wajib dipilih.</p>
|
||||
<label class="block text-xs font-semibold text-gray-600 mb-1">RT</label>
|
||||
<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 text-sm bg-gray-100 text-gray-700 cursor-not-allowed"
|
||||
readonly>
|
||||
<p class="text-[11px] text-gray-400 mt-1">RT mengikuti akun RT yang sedang login.</p>
|
||||
</div>
|
||||
|
||||
{{-- NO KK --}}
|
||||
|
|
@ -113,7 +111,7 @@ class="input-field w-full border border-gray-200 rounded-xl px-3 py-2 text-sm fo
|
|||
<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>
|
||||
<input type="date" name="tanggal_lahir" id="tanggal_lahir"
|
||||
value="{{ old('tanggal_lahir', $calonPenerima->tanggal_lahir) }}"
|
||||
value="{{ old('tanggal_lahir', $calonPenerima->tanggal_lahir ? \Carbon\Carbon::parse($calonPenerima->tanggal_lahir)->format('Y-m-d') : '') }}"
|
||||
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>
|
||||
<p class="error-msg text-xs text-red-500 mt-1 hidden">Tanggal lahir wajib diisi.</p>
|
||||
|
|
@ -162,13 +160,14 @@ class="input-field w-full border border-gray-200 rounded-xl px-3 py-2 text-sm fo
|
|||
<p class="error-msg text-xs text-red-500 mt-1 hidden">Alamat minimal 5 karakter.</p>
|
||||
</div>
|
||||
|
||||
{{-- DUSUN (readonly) --}}
|
||||
<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</label>
|
||||
<input type="text" name="desa" id="desa"
|
||||
value="{{ old('desa', $calonPenerima->desa) }}"
|
||||
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 dusun" required>
|
||||
<p class="error-msg text-xs text-red-500 mt-1 hidden">Dusun wajib diisi.</p>
|
||||
class="w-full border border-gray-200 rounded-xl px-3 py-2 text-sm bg-gray-100 text-gray-700 cursor-not-allowed"
|
||||
readonly>
|
||||
<p class="text-[11px] text-gray-400 mt-1">Dusun mengikuti RT dan tidak dapat diubah.</p>
|
||||
</div>
|
||||
|
||||
<div class="field-group">
|
||||
|
|
@ -264,12 +263,14 @@ function showError(id, msg) {
|
|||
if (input) input.classList.add('border-red-400', 'bg-red-50');
|
||||
if (err) { err.textContent = msg; err.classList.remove('hidden'); }
|
||||
}
|
||||
|
||||
function clearError(id) {
|
||||
const input = document.getElementById(id);
|
||||
const err = input?.parentElement?.querySelector('.error-msg');
|
||||
if (input) input.classList.remove('border-red-400', 'bg-red-50');
|
||||
if (err) err.classList.add('hidden');
|
||||
}
|
||||
|
||||
function showToast(msg) {
|
||||
const t = document.getElementById('toast');
|
||||
document.getElementById('toast-msg').textContent = msg;
|
||||
|
|
@ -278,20 +279,19 @@ function showToast(msg) {
|
|||
}
|
||||
|
||||
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.',
|
||||
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.',
|
||||
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.',
|
||||
};
|
||||
|
||||
Object.keys(rules).forEach(id => {
|
||||
|
|
|
|||
|
|
@ -1,176 +1,219 @@
|
|||
<x-app-layout>
|
||||
<x-slot name="header">
|
||||
<div class="flex justify-between items-center">
|
||||
<div style="display:flex;align-items:center;justify-content:space-between;gap:16px;">
|
||||
<div>
|
||||
<h2 class="font-semibold text-xl text-gray-800 leading-tight">Data Calon Penerima</h2>
|
||||
<p class="text-sm text-gray-500 mt-0.5">Daftar seluruh warga yang telah didaftarkan.</p>
|
||||
<h2 style="font-size:17px;font-weight:800;color:#111827;line-height:1.2;">Data Calon Penerima</h2>
|
||||
<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') }}"
|
||||
class="inline-flex items-center gap-2 px-4 py-2 bg-blue-600 text-white text-sm font-semibold 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="M12 4v16m8-8H4"/>
|
||||
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">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2.5" d="M12 4v16m8-8H4"/>
|
||||
</svg>
|
||||
Tambah Data
|
||||
</a>
|
||||
</div>
|
||||
</x-slot>
|
||||
|
||||
{{-- NOTIFIKASI --}}
|
||||
@if (session('success'))
|
||||
<div class="mb-4 flex items-center gap-3 rounded-xl border border-green-200 bg-green-50 px-4 py-3 text-sm text-green-700">
|
||||
<svg class="w-4 h-4 shrink-0 text-green-500" 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>
|
||||
<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;}
|
||||
|
||||
/* Table */
|
||||
table{width:100%;border-collapse:collapse;}
|
||||
thead tr{background:#f8fafc;border-bottom:1.5px solid #f1f5f9;}
|
||||
thead th{padding:10px 14px;text-align:left;font-size:10px;font-weight:700;color:#9ca3af;text-transform:uppercase;letter-spacing:.08em;white-space:nowrap;}
|
||||
tbody tr{border-bottom:1px solid #f8fafc;transition:background .12s;}
|
||||
tbody tr:hover{background:#f0f7ff;}
|
||||
tbody tr:last-child{border-bottom:none;}
|
||||
tbody td{padding:11px 14px;vertical-align:middle;}
|
||||
|
||||
/* 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;}
|
||||
|
||||
/* Tracking badges */
|
||||
.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;}
|
||||
|
||||
/* Status badges */
|
||||
.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: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;}
|
||||
.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 */
|
||||
.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 wrapper */
|
||||
.pagi{padding:10px 16px;background:#fafafa;border-top:1px solid #f1f5f9;}
|
||||
</style>
|
||||
|
||||
{{-- FLASH --}}
|
||||
@if(session('success'))
|
||||
<div class="flash" style="background:#f0fdf4;border:1.5px solid #bbf7d0;color:#166534;">
|
||||
<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>
|
||||
{{ session('success') }}
|
||||
</div>
|
||||
@endif
|
||||
|
||||
@if (session('error'))
|
||||
<div class="mb-4 flex items-center 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 text-red-500" 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>
|
||||
@if(session('error'))
|
||||
<div class="flash" style="background:#fff1f2;border:1.5px solid #fecdd3;color:#9f1239;">
|
||||
<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
|
||||
|
||||
<div class="bg-white shadow-sm rounded-2xl border border-gray-100 overflow-hidden">
|
||||
<div class="idx-card">
|
||||
|
||||
{{-- TABLE HEADER INFO --}}
|
||||
<div class="px-5 py-3 border-b border-gray-100 bg-gray-50 flex items-center justify-between">
|
||||
<div class="flex items-center gap-2">
|
||||
<div class="w-1 h-4 bg-blue-600 rounded-full"></div>
|
||||
<span class="text-sm font-semibold text-gray-700">
|
||||
Total: {{ $calonPenerimas->total() }} data
|
||||
{{-- 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;">
|
||||
<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>
|
||||
</div>
|
||||
<span class="text-xs text-gray-400">
|
||||
Halaman {{ $calonPenerimas->currentPage() }} dari {{ $calonPenerimas->lastPage() }}
|
||||
<span style="font-size:11px;color:#9ca3af;">
|
||||
Halaman {{ $calonPenerimas->currentPage() }} / {{ $calonPenerimas->lastPage() }}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div class="overflow-x-auto">
|
||||
<table class="min-w-full text-sm text-gray-700">
|
||||
{{-- TABLE --}}
|
||||
<div style="overflow-x:auto;">
|
||||
<table>
|
||||
<thead>
|
||||
<tr class="bg-gray-50 border-b border-gray-100">
|
||||
<th class="px-4 py-3 text-left text-xs font-semibold text-gray-500 uppercase tracking-wide">No</th>
|
||||
<th class="px-4 py-3 text-left text-xs font-semibold text-gray-500 uppercase tracking-wide">Nama</th>
|
||||
<th class="px-4 py-3 text-left text-xs font-semibold text-gray-500 uppercase tracking-wide">NIK</th>
|
||||
<th class="px-4 py-3 text-left text-xs font-semibold text-gray-500 uppercase tracking-wide">RT</th>
|
||||
<th class="px-4 py-3 text-left text-xs font-semibold text-gray-500 uppercase tracking-wide">Dusun</th>
|
||||
<th class="px-4 py-3 text-left text-xs font-semibold text-gray-500 uppercase tracking-wide">Status</th>
|
||||
<th class="px-4 py-3 text-left text-xs font-semibold text-gray-500 uppercase tracking-wide">Aksi</th>
|
||||
<tr>
|
||||
<th style="width:40px;">#</th>
|
||||
<th>Nama</th>
|
||||
<th>NIK</th>
|
||||
<th>RT</th>
|
||||
<th>Dusun</th>
|
||||
<th>Tracking</th>
|
||||
<th>Status</th>
|
||||
<th>Aksi</th>
|
||||
</tr>
|
||||
</thead>
|
||||
|
||||
<tbody class="divide-y divide-gray-50">
|
||||
@forelse ($calonPenerimas as $index => $item)
|
||||
<tr class="hover:bg-blue-50/30 transition-colors duration-150">
|
||||
|
||||
{{-- NO --}}
|
||||
<td class="px-4 py-3 text-gray-400 text-xs">
|
||||
<tbody>
|
||||
@forelse($calonPenerimas as $index => $item)
|
||||
<tr>
|
||||
{{-- No --}}
|
||||
<td style="font-size:11px;color:#9ca3af;font-weight:600;text-align:center;">
|
||||
{{ $calonPenerimas->firstItem() + $index }}
|
||||
</td>
|
||||
|
||||
{{-- NAMA --}}
|
||||
<td class="px-4 py-3">
|
||||
<div class="flex items-center gap-2.5">
|
||||
<div class="w-8 h-8 rounded-full bg-gradient-to-br from-blue-500 to-blue-600 flex items-center justify-center shrink-0">
|
||||
<span class="text-xs font-bold text-white">
|
||||
{{ strtoupper(substr($item->nama_lengkap, 0, 1)) }}
|
||||
</span>
|
||||
</div>
|
||||
<span class="font-medium text-gray-800">{{ $item->nama_lengkap }}</span>
|
||||
{{-- Nama --}}
|
||||
<td>
|
||||
<div style="display:flex;align-items:center;gap:9px;">
|
||||
<div class="av">{{ strtoupper(substr($item->nama_lengkap, 0, 1)) }}</div>
|
||||
<span style="font-size:13px;font-weight:600;color:#111827;">{{ $item->nama_lengkap }}</span>
|
||||
</div>
|
||||
</td>
|
||||
|
||||
{{-- NIK --}}
|
||||
<td class="px-4 py-3 text-gray-500 font-mono text-xs">
|
||||
<td style="font-family:monospace;font-size:11.5px;color:#6b7280;letter-spacing:.02em;">
|
||||
{{ $item->nik }}
|
||||
</td>
|
||||
|
||||
{{-- RT --}}
|
||||
<td class="px-4 py-3">
|
||||
<span class="inline-flex items-center px-2.5 py-0.5 rounded-lg bg-blue-50 text-blue-700 text-xs font-semibold border border-blue-100">
|
||||
RT {{ str_pad($item->rt->nomor_rt ?? '-', 3, '0', STR_PAD_LEFT) }}
|
||||
</span>
|
||||
<td>
|
||||
<span class="badge-rt">RT {{ str_pad($item->rt->nomor_rt ?? '-', 3, '0', STR_PAD_LEFT) }}</span>
|
||||
</td>
|
||||
|
||||
{{-- DUSUN --}}
|
||||
<td class="px-4 py-3 text-gray-600 text-xs">
|
||||
{{-- Dusun --}}
|
||||
<td style="font-size:12px;color:#6b7280;">
|
||||
{{ $item->rt->dusun->nama_dusun ?? '-' }}
|
||||
</td>
|
||||
|
||||
{{-- STATUS --}}
|
||||
<td class="px-4 py-3">
|
||||
@if ($item->status_verifikasi === 'pending')
|
||||
<span class="inline-flex items-center gap-1 px-2.5 py-1 text-xs font-semibold rounded-full bg-amber-100 text-amber-700">
|
||||
<span class="w-1.5 h-1.5 rounded-full bg-amber-500"></span>
|
||||
Pending
|
||||
</span>
|
||||
@elseif ($item->status_verifikasi === 'disetujui')
|
||||
<span class="inline-flex items-center gap-1 px-2.5 py-1 text-xs font-semibold rounded-full bg-emerald-100 text-emerald-700">
|
||||
<span class="w-1.5 h-1.5 rounded-full bg-emerald-500"></span>
|
||||
Disetujui
|
||||
</span>
|
||||
{{-- Tracking --}}
|
||||
<td>
|
||||
@php $trk = $item->tracking_status ?? 'draft'; @endphp
|
||||
@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="inline-flex items-center gap-1 px-2.5 py-1 text-xs font-semibold rounded-full bg-rose-100 text-rose-700">
|
||||
<span class="w-1.5 h-1.5 rounded-full bg-rose-500"></span>
|
||||
Ditolak
|
||||
</span>
|
||||
<span class="badge trk-draft"><span class="dot"></span>—</span>
|
||||
@endif
|
||||
</td>
|
||||
|
||||
{{-- AKSI --}}
|
||||
<td class="px-4 py-3">
|
||||
<div class="flex items-center gap-1.5">
|
||||
<a href="{{ route('rt.calon-penerima.show', $item->id) }}"
|
||||
class="inline-flex items-center gap-1 px-2.5 py-1 text-xs font-medium bg-gray-100 text-gray-600 rounded-lg hover:bg-gray-200 transition">
|
||||
<svg class="w-3 h-3" 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>
|
||||
{{-- Status --}}
|
||||
<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>
|
||||
@endif
|
||||
</td>
|
||||
|
||||
{{-- Aksi --}}
|
||||
<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 ($item->status_verifikasi === 'pending')
|
||||
<a href="{{ route('rt.calon-penerima.edit', $item->id) }}"
|
||||
class="inline-flex items-center gap-1 px-2.5 py-1 text-xs font-medium bg-blue-100 text-blue-700 rounded-lg hover:bg-blue-200 transition">
|
||||
<svg class="w-3 h-3" 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>
|
||||
@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 action="{{ route('rt.calon-penerima.destroy', $item->id) }}"
|
||||
method="POST"
|
||||
onsubmit="return confirm('Yakin ingin menghapus data {{ $item->nama_lengkap }}?')">
|
||||
@csrf
|
||||
@method('DELETE')
|
||||
<button type="submit"
|
||||
class="inline-flex items-center gap-1 px-2.5 py-1 text-xs font-medium bg-rose-100 text-rose-700 rounded-lg hover:bg-rose-200 transition">
|
||||
<svg class="w-3 h-3" 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>
|
||||
<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>
|
||||
</td>
|
||||
|
||||
</tr>
|
||||
@empty
|
||||
<tr>
|
||||
<td colspan="7" class="px-4 py-14 text-center">
|
||||
<div class="flex flex-col items-center gap-2">
|
||||
<div class="w-12 h-12 bg-gray-100 rounded-full flex items-center justify-center">
|
||||
<svg class="w-6 h-6 text-gray-400" 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>
|
||||
<td colspan="8">
|
||||
<div class="empty-state">
|
||||
<div class="empty-icon">
|
||||
<svg 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>
|
||||
</div>
|
||||
<p class="text-sm font-medium text-gray-500">Belum ada data calon penerima</p>
|
||||
<a href="{{ route('rt.calon-penerima.create') }}" class="text-xs text-blue-600 hover:underline">+ Tambah sekarang</a>
|
||||
<p style="font-size:13px;font-weight:600;color:#6b7280;margin-bottom:6px;">Belum ada data calon penerima</p>
|
||||
<a href="{{ route('rt.calon-penerima.create') }}" style="font-size:12px;color:#2563eb;font-weight:600;text-decoration:none;">+ Tambah sekarang</a>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
|
|
@ -180,9 +223,10 @@ class="inline-flex items-center gap-1 px-2.5 py-1 text-xs font-medium bg-rose-10
|
|||
</div>
|
||||
|
||||
{{-- PAGINATION --}}
|
||||
<div class="px-5 py-3 border-t border-gray-100 bg-gray-50">
|
||||
<div class="pagi">
|
||||
{{ $calonPenerimas->links() }}
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
</x-app-layout>
|
||||
|
|
@ -1,290 +1,413 @@
|
|||
<x-app-layout>
|
||||
<x-slot name="header">
|
||||
<div class="flex items-start justify-between gap-4">
|
||||
<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">Detail data pendataan warga dan hasil prediksi.</p>
|
||||
</div>
|
||||
<div class="flex gap-2">
|
||||
<div style="display:flex; align-items:center; justify-content:space-between; gap:16px;">
|
||||
<div style="display:flex; align-items:center; gap:12px;">
|
||||
<a href="{{ route('rt.calon-penerima.index') }}"
|
||||
class="inline-flex items-center gap-1.5 px-4 py-2 bg-white border border-gray-200 text-gray-600 text-sm font-medium rounded-xl hover:bg-gray-50 transition">
|
||||
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
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;">
|
||||
<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>
|
||||
Kembali
|
||||
</a>
|
||||
@if(($calonPenerima->status_verifikasi ?? '') === 'pending')
|
||||
<a href="{{ route('calon-penerima.edit', $calonPenerima->id) }}"
|
||||
class="inline-flex items-center gap-1.5 px-4 py-2 bg-blue-600 text-white text-sm font-semibold 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="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>
|
||||
@endif
|
||||
<div>
|
||||
<h2 style="font-size:17px; font-weight:800; color:#111827; line-height:1.2;">Detail Calon Penerima</h2>
|
||||
<p style="font-size:11px; color:#9ca3af; margin-top:2px;">Data pendataan warga & hasil prediksi kelayakan</p>
|
||||
</div>
|
||||
</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);">
|
||||
<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
|
||||
</div>
|
||||
</x-slot>
|
||||
|
||||
{{-- NOTIFIKASI --}}
|
||||
@if (session('success'))
|
||||
<div class="mb-4 flex items-center gap-3 rounded-xl border border-green-200 bg-green-50 px-4 py-3 text-sm text-green-700">
|
||||
<svg class="w-4 h-4 shrink-0 text-green-500" 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>
|
||||
<style>
|
||||
.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;}
|
||||
.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;}
|
||||
.g2{display:grid;grid-template-columns:1fr 1fr;gap:12px;}
|
||||
.g4{display:grid;grid-template-columns:repeat(4,1fr);gap:12px;}
|
||||
.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;}
|
||||
.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;}
|
||||
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;}
|
||||
.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;}
|
||||
@media(max-width:1024px){.mg{grid-template-columns:1fr!important;}.g4{grid-template-columns:1fr 1fr!important;}}
|
||||
@media(max-width:600px){.g2,.g4{grid-template-columns:1fr!important;}}
|
||||
</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'
|
||||
};
|
||||
@endphp
|
||||
|
||||
{{-- FLASH --}}
|
||||
@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 class="mb-4 flex items-center 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 text-red-500" 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>
|
||||
@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
|
||||
|
||||
{{-- IDENTITY BANNER --}}
|
||||
<div class="bg-gradient-to-br from-blue-600 to-blue-500 rounded-2xl p-5 mb-4 flex items-center gap-4 text-white shadow-lg shadow-blue-200">
|
||||
<div class="w-14 h-14 rounded-2xl bg-white/20 flex items-center justify-center text-2xl font-bold shrink-0">
|
||||
{{-- 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;display:flex;align-items:center;gap:14px;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 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>
|
||||
<div class="flex-1 min-w-0">
|
||||
<h3 class="text-lg font-bold truncate">{{ $calonPenerima->nama_lengkap ?? '-' }}</h3>
|
||||
<p class="text-blue-100 text-sm font-mono mt-0.5">NIK: {{ $calonPenerima->nik ?? '-' }}</p>
|
||||
|
||||
<div style="flex:1;min-width:0;">
|
||||
<h3 style="font-size:16px;font-weight:900;color:#fff;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;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;">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>
|
||||
</div>
|
||||
</div>
|
||||
<div class="shrink-0">
|
||||
@php $st = $calonPenerima->status_verifikasi ?? 'pending'; @endphp
|
||||
@if ($st === 'pending')
|
||||
<span class="inline-flex items-center gap-1.5 px-3 py-1.5 rounded-full text-xs font-semibold bg-amber-100 text-amber-700">
|
||||
<span class="w-1.5 h-1.5 rounded-full bg-amber-500"></span> Pending
|
||||
</span>
|
||||
@elseif ($st === 'disetujui')
|
||||
<span class="inline-flex items-center gap-1.5 px-3 py-1.5 rounded-full text-xs font-semibold bg-emerald-100 text-emerald-700">
|
||||
<span class="w-1.5 h-1.5 rounded-full bg-emerald-500"></span> Disetujui
|
||||
</span>
|
||||
|
||||
<div style="display:flex;align-items:center;gap:12px;flex-shrink:0;">
|
||||
@if($probPct !== null)
|
||||
<div style="text-align:right;">
|
||||
<div 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>
|
||||
@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="inline-flex items-center gap-1.5 px-3 py-1.5 rounded-full text-xs font-semibold bg-rose-100 text-rose-700">
|
||||
<span class="w-1.5 h-1.5 rounded-full bg-rose-500"></span> Ditolak
|
||||
</span>
|
||||
<span class="pill" style="background:#eff6ff;color:#1d4ed8;border:1px solid #bfdbfe;"><span class="dot" style="background:#3b82f6;"></span>{{ $trackingLabel }}</span>
|
||||
@endif
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="grid grid-cols-1 lg:grid-cols-3 gap-4">
|
||||
{{-- MAIN GRID --}}
|
||||
<div class="mg" style="display:grid;grid-template-columns:1fr 310px;gap:12px;align-items:start;">
|
||||
|
||||
{{-- KOLOM KIRI: DATA UTAMA --}}
|
||||
<div class="lg:col-span-2 space-y-4">
|
||||
{{-- KIRI --}}
|
||||
<div style="display:flex;flex-direction:column;gap:12px;">
|
||||
|
||||
{{-- IDENTITAS --}}
|
||||
<div class="bg-white rounded-2xl shadow-sm border border-gray-100 overflow-hidden">
|
||||
<div class="px-5 py-3 border-b border-gray-100 flex items-center gap-2 bg-gray-50">
|
||||
<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 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;">{{ $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>
|
||||
</div>
|
||||
</div>
|
||||
<div class="p-5 grid grid-cols-2 md:grid-cols-4 gap-4">
|
||||
@php
|
||||
$identitas = [
|
||||
'No. KK' => $calonPenerima->no_kk ?? '-',
|
||||
'Jenis Kelamin' => $calonPenerima->jenis_kelamin ?? '-',
|
||||
'Tempat Lahir' => $calonPenerima->tempat_lahir ?? '-',
|
||||
'Tanggal Lahir' => $calonPenerima->tanggal_lahir
|
||||
? \Carbon\Carbon::parse($calonPenerima->tanggal_lahir)->translatedFormat('d F Y')
|
||||
: '-',
|
||||
'Usia' => ($calonPenerima->usia ?? '-') . ' tahun',
|
||||
'Status Perkawinan' => $calonPenerima->status_perkawinan ?? '-',
|
||||
];
|
||||
@endphp
|
||||
@foreach($identitas as $label => $val)
|
||||
|
||||
{{-- 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>
|
||||
</div>
|
||||
</div>
|
||||
<div><p class="lbl">Aset Kepemilikan</p><p class="val">{{ $calonPenerima->aset_kepemilikan ?? '-' }}</p></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>
|
||||
<div class="text-xs text-gray-400 mb-0.5">{{ $label }}</div>
|
||||
<div class="text-sm font-semibold text-gray-800">{{ $val }}</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>
|
||||
@endforeach
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{-- TEMPAT TINGGAL --}}
|
||||
<div class="bg-white rounded-2xl shadow-sm border border-gray-100 overflow-hidden">
|
||||
<div class="px-5 py-3 border-b border-gray-100 flex items-center gap-2 bg-gray-50">
|
||||
<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="p-5 grid grid-cols-2 md:grid-cols-4 gap-4">
|
||||
<div class="col-span-2">
|
||||
<div class="text-xs text-gray-400 mb-0.5">Alamat</div>
|
||||
<div class="text-sm font-semibold text-gray-800">{{ $calonPenerima->alamat ?? '-' }}</div>
|
||||
</div>
|
||||
<div>
|
||||
<div class="text-xs text-gray-400 mb-0.5">Dusun</div>
|
||||
<div class="text-sm font-semibold text-gray-800">{{ $calonPenerima->desa ?? '-' }}</div>
|
||||
</div>
|
||||
<div>
|
||||
<div class="text-xs text-gray-400 mb-0.5">RT</div>
|
||||
<div class="text-sm font-semibold text-gray-800">
|
||||
<span class="inline-flex items-center px-2.5 py-0.5 rounded-lg bg-blue-50 text-blue-700 text-xs font-semibold border border-blue-100">
|
||||
RT {{ str_pad($calonPenerima->rt->nomor_rt ?? 0, 3, '0', STR_PAD_LEFT) }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-span-2">
|
||||
<div class="text-xs text-gray-400 mb-0.5">Aset Kepemilikan</div>
|
||||
<div class="text-sm font-semibold text-gray-800">{{ $calonPenerima->aset_kepemilikan ?? '-' }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{-- EKONOMI --}}
|
||||
<div class="bg-white rounded-2xl shadow-sm border border-gray-100 overflow-hidden">
|
||||
<div class="px-5 py-3 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>
|
||||
</div>
|
||||
<div class="p-5 grid grid-cols-2 md:grid-cols-4 gap-4">
|
||||
<div>
|
||||
<div class="text-xs text-gray-400 mb-0.5">Pekerjaan</div>
|
||||
<div class="text-sm font-semibold text-gray-800">{{ $calonPenerima->pekerjaan ?? '-' }}</div>
|
||||
</div>
|
||||
<div>
|
||||
<div class="text-xs text-gray-400 mb-0.5">Penghasilan</div>
|
||||
<div class="text-sm font-semibold text-gray-800">
|
||||
Rp {{ number_format((float)($calonPenerima->penghasilan ?? 0), 0, ',', '.') }}
|
||||
{{-- Tracking Pengajuan --}}
|
||||
<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 class="text-xs text-gray-400 mb-0.5">Jumlah Tanggungan</div>
|
||||
<div class="text-sm font-semibold text-gray-800">{{ $calonPenerima->jumlah_tanggungan ?? '-' }} orang</div>
|
||||
</div>
|
||||
<div>
|
||||
<div class="text-xs text-gray-400 mb-0.5">Bantuan Lain</div>
|
||||
@if(($calonPenerima->bantuan_lain ?? '') === 'ya')
|
||||
<span class="inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-semibold bg-amber-100 text-amber-700">Ya</span>
|
||||
</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" style="display:flex;flex-wrap:wrap;align-items:center;gap:14px;">
|
||||
@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="inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-semibold bg-gray-100 text-gray-600">Tidak</span>
|
||||
<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 style="display:flex;gap:18px;">
|
||||
<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>
|
||||
</div>
|
||||
|
||||
@if(!empty($calonPenerima->catatan_admin))
|
||||
<div style="flex:1;min-width:160px;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>
|
||||
|
||||
{{-- KOLOM KANAN --}}
|
||||
<div class="space-y-4">
|
||||
{{-- KANAN --}}
|
||||
<div style="display:flex;flex-direction:column;gap:12px;">
|
||||
|
||||
{{-- STATUS PENGAJUAN --}}
|
||||
<div class="bg-white rounded-2xl shadow-sm border border-gray-100 overflow-hidden">
|
||||
<div class="px-5 py-3 border-b border-gray-100 flex items-center gap-2 bg-gray-50">
|
||||
<div class="w-1 h-4 bg-gray-700 rounded-full"></div>
|
||||
<h3 class="text-sm font-semibold text-gray-700">Status Pengajuan</h3>
|
||||
</div>
|
||||
<div class="p-5 space-y-3">
|
||||
@if ($st === 'pending')
|
||||
<div class="flex items-center gap-2 p-3 rounded-xl bg-amber-50 border border-amber-100">
|
||||
<span class="w-2 h-2 rounded-full bg-amber-500 shrink-0"></span>
|
||||
<span class="text-sm font-semibold text-amber-700">Menunggu Verifikasi</span>
|
||||
</div>
|
||||
@elseif ($st === 'disetujui')
|
||||
<div class="flex items-center gap-2 p-3 rounded-xl bg-emerald-50 border border-emerald-100">
|
||||
<span class="w-2 h-2 rounded-full bg-emerald-500 shrink-0"></span>
|
||||
<span class="text-sm font-semibold text-emerald-700">Pengajuan Disetujui</span>
|
||||
</div>
|
||||
@else
|
||||
<div class="flex items-center gap-2 p-3 rounded-xl bg-rose-50 border border-rose-100">
|
||||
<span class="w-2 h-2 rounded-full bg-rose-500 shrink-0"></span>
|
||||
<span class="text-sm font-semibold text-rose-700">Pengajuan Ditolak</span>
|
||||
</div>
|
||||
@endif
|
||||
|
||||
<div class="text-xs text-gray-400 space-y-1">
|
||||
<div class="flex justify-between">
|
||||
<span>Dibuat</span>
|
||||
<span class="text-gray-600 font-medium">
|
||||
{{ $calonPenerima->created_at ? \Carbon\Carbon::parse($calonPenerima->created_at)->translatedFormat('d M Y, H:i') : '-' }}
|
||||
</span>
|
||||
</div>
|
||||
<div class="flex justify-between">
|
||||
<span>Diperbarui</span>
|
||||
<span class="text-gray-600 font-medium">
|
||||
{{ $calonPenerima->updated_at ? \Carbon\Carbon::parse($calonPenerima->updated_at)->translatedFormat('d M Y, H:i') : '-' }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@if(!empty($calonPenerima->catatan_admin))
|
||||
<div class="rounded-xl border border-gray-100 bg-gray-50 p-3 text-xs text-gray-600">
|
||||
<div class="font-semibold text-gray-700 mb-1">Catatan Admin</div>
|
||||
{{ $calonPenerima->catatan_admin }}
|
||||
</div>
|
||||
@endif
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{-- PREDIKSI --}}
|
||||
<div class="bg-white rounded-2xl shadow-sm border border-gray-100 overflow-hidden">
|
||||
<div class="px-5 py-3 border-b border-gray-100 flex items-center gap-2 bg-gray-50">
|
||||
<div class="w-1 h-4 bg-blue-600 rounded-full"></div>
|
||||
<h3 class="text-sm font-semibold text-gray-700">Prediksi Kelayakan</h3>
|
||||
</div>
|
||||
<div class="p-5">
|
||||
@php
|
||||
$pred = $calonPenerima->prediksiKelayakan ?? null;
|
||||
$prob = $pred ? (float)$pred->probability : null;
|
||||
$rec = $pred ? ($pred->recommendation ?? '-') : null;
|
||||
$probPct = $prob !== null ? ($prob <= 1 ? $prob * 100 : $prob) : null;
|
||||
@endphp
|
||||
|
||||
@if($pred)
|
||||
<div class="text-center py-2">
|
||||
<div class="text-4xl font-bold text-gray-900">{{ number_format($probPct, 1) }}<span class="text-xl text-gray-400">%</span></div>
|
||||
<div class="text-xs text-gray-400 mt-0.5">Probabilitas Kelayakan</div>
|
||||
</div>
|
||||
|
||||
{{-- Progress bar --}}
|
||||
<div class="mt-3 mb-4">
|
||||
<div class="w-full bg-gray-100 rounded-full h-2">
|
||||
<div class="h-2 rounded-full transition-all duration-500
|
||||
{{ $probPct >= 70 ? 'bg-emerald-500' : ($probPct >= 40 ? 'bg-amber-500' : 'bg-rose-500') }}"
|
||||
style="width: {{ min($probPct, 100) }}%">
|
||||
{{-- 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;">
|
||||
<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>
|
||||
|
||||
<div class="p-3 rounded-xl border text-sm font-semibold text-center
|
||||
{{ $probPct >= 70 ? 'bg-emerald-50 border-emerald-100 text-emerald-700' : ($probPct >= 40 ? 'bg-amber-50 border-amber-100 text-amber-700' : 'bg-rose-50 border-rose-100 text-rose-700') }}">
|
||||
{{ $rec }}
|
||||
</div>
|
||||
@else
|
||||
<div class="py-6 text-center">
|
||||
<div class="w-10 h-10 bg-gray-100 rounded-full flex items-center justify-center mx-auto mb-2">
|
||||
<svg class="w-5 h-5 text-gray-400" fill="none" stroke="currentColor" 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>
|
||||
@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>
|
||||
<p class="text-xs text-gray-400">Belum ada data prediksi</p>
|
||||
@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'] 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;">{{ $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>
|
||||
</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
|
||||
</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>
|
||||
@endif
|
||||
</div>
|
||||
<p style="font-size:11px;color:#9ca3af;">Belum ada data prediksi</p>
|
||||
</div>
|
||||
@endif
|
||||
</div>
|
||||
|
||||
{{-- AKSI HAPUS --}}
|
||||
@if(($calonPenerima->status_verifikasi ?? '') === 'pending')
|
||||
<div class="bg-white rounded-2xl shadow-sm border border-rose-100 overflow-hidden">
|
||||
<div class="px-5 py-3 border-b border-rose-100 flex items-center gap-2 bg-rose-50">
|
||||
<div class="w-1 h-4 bg-rose-500 rounded-full"></div>
|
||||
<h3 class="text-sm font-semibold text-rose-700">Zona Bahaya</h3>
|
||||
{{-- 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>
|
||||
@endif
|
||||
</div>
|
||||
<div class="p-5">
|
||||
<p class="text-xs text-gray-500 mb-3">Hapus data ini secara permanen. Tindakan ini tidak dapat dibatalkan.</p>
|
||||
<form action="{{ route('calon-penerima.destroy', $calonPenerima->id) }}"
|
||||
method="POST"
|
||||
onsubmit="return confirm('Yakin ingin menghapus data {{ $calonPenerima->nama_lengkap }}? Tindakan ini tidak dapat dibatalkan.')">
|
||||
@csrf
|
||||
@method('DELETE')
|
||||
<button type="submit"
|
||||
class="w-full inline-flex items-center justify-center gap-2 px-4 py-2 rounded-xl bg-rose-600 text-white text-sm font-semibold hover:bg-rose-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>
|
||||
</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 }}?')">
|
||||
@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>
|
||||
Hapus Data
|
||||
</button>
|
||||
</form>
|
||||
|
|
@ -294,4 +417,5 @@ class="w-full inline-flex items-center justify-center gap-2 px-4 py-2 rounded-xl
|
|||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</x-app-layout>
|
||||
|
|
@ -10,13 +10,10 @@
|
|||
use App\Http\Controllers\Admin\AdminController;
|
||||
use App\Http\Controllers\Admin\FilterisasiController;
|
||||
|
||||
// RT Controllers (folder Rt)
|
||||
// RT Controllers
|
||||
use App\Http\Controllers\Rt\DashboardController as RtDashboardController;
|
||||
use App\Http\Controllers\Rt\CalonPenerimaController as RtCalonPenerimaController;
|
||||
|
||||
// Profile default (bawaan Breeze/Jetstream)
|
||||
use App\Http\Controllers\ProfileController as UserProfileController;
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| ROOT
|
||||
|
|
@ -49,7 +46,6 @@
|
|||
abort(403, 'Role tidak dikenali.');
|
||||
})->middleware(['auth', 'verified'])->name('dashboard');
|
||||
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| ADMIN ROUTES (ADMIN ONLY)
|
||||
|
|
@ -67,18 +63,28 @@
|
|||
abort(403, 'Akses ditolak: hanya Admin.');
|
||||
}
|
||||
|
||||
$totalWarga = CalonPenerima::count();
|
||||
$totalDusun = Dusun::count();
|
||||
$totalPenerima = CalonPenerima::where('status_verifikasi', 'disetujui')->count();
|
||||
$totalPending = CalonPenerima::where('status_verifikasi', 'pending')->count();
|
||||
$visibleStatuses = ['terkirim', 'sedang_validasi', 'selesai'];
|
||||
|
||||
$totalWarga = CalonPenerima::whereIn('tracking_status', $visibleStatuses)->count();
|
||||
$totalDusun = Dusun::count();
|
||||
$totalPenerima = CalonPenerima::whereIn('tracking_status', $visibleStatuses)
|
||||
->where('status_verifikasi', 'disetujui')
|
||||
->count();
|
||||
$totalPending = CalonPenerima::whereIn('tracking_status', $visibleStatuses)
|
||||
->where('status_verifikasi', 'pending')
|
||||
->count();
|
||||
|
||||
$statusDistribution = CalonPenerima::select('status_verifikasi as status', DB::raw('COUNT(*) as total'))
|
||||
->whereIn('tracking_status', $visibleStatuses)
|
||||
->groupBy('status_verifikasi')
|
||||
->get();
|
||||
|
||||
$wargaPerDusun = DB::table('dusuns')
|
||||
->leftJoin('rts', 'rts.dusun_id', '=', 'dusuns.id')
|
||||
->leftJoin('calon_penerimas', 'calon_penerimas.rt_id', '=', 'rts.id')
|
||||
->leftJoin('calon_penerimas', function ($join) use ($visibleStatuses) {
|
||||
$join->on('calon_penerimas.rt_id', '=', 'rts.id')
|
||||
->whereIn('calon_penerimas.tracking_status', $visibleStatuses);
|
||||
})
|
||||
->select('dusuns.nama_dusun as dusun', DB::raw('COUNT(calon_penerimas.id) as total'))
|
||||
->groupBy('dusuns.id', 'dusuns.nama_dusun')
|
||||
->orderBy('dusuns.nama_dusun')
|
||||
|
|
@ -97,6 +103,7 @@
|
|||
'prediksi_kelayakans.probability as probabilitas',
|
||||
'calon_penerimas.status_verifikasi as status'
|
||||
)
|
||||
->whereIn('calon_penerimas.tracking_status', $visibleStatuses)
|
||||
->orderBy('prediksi_kelayakans.probability', 'desc')
|
||||
->limit(10)
|
||||
->get();
|
||||
|
|
@ -112,7 +119,6 @@
|
|||
));
|
||||
})->name('dashboard');
|
||||
|
||||
|
||||
// DATA WARGA + SEARCH
|
||||
Route::get('/data-warga', function (Request $request) {
|
||||
$user = $request->user();
|
||||
|
|
@ -124,6 +130,7 @@
|
|||
$q = trim((string) $request->query('q', ''));
|
||||
|
||||
$wargasQuery = CalonPenerima::with(['rt.dusun', 'prediksiKelayakan', 'user'])
|
||||
->whereIn('tracking_status', ['terkirim', 'sedang_validasi', 'selesai'])
|
||||
->latest();
|
||||
|
||||
if ($q !== '') {
|
||||
|
|
@ -139,48 +146,70 @@
|
|||
}
|
||||
|
||||
$wargas = $wargasQuery->paginate(10)->withQueryString();
|
||||
|
||||
return view('admin.data-warga', compact('dusuns', 'wargas'));
|
||||
})->name('data-warga');
|
||||
|
||||
|
||||
// SETUJUI / TOLAK
|
||||
Route::post('/data-warga/{id}/setujui', function (Request $request, $id) {
|
||||
// MULAI VALIDASI
|
||||
Route::post('/data-warga/{id}/mulai-validasi', function (Request $request, $id) {
|
||||
$user = $request->user();
|
||||
if (!$user || $user->role !== 'admin') {
|
||||
abort(403, 'Akses ditolak: hanya Admin.');
|
||||
}
|
||||
|
||||
$warga = CalonPenerima::findOrFail($id);
|
||||
$warga->update(['status_verifikasi' => 'disetujui']);
|
||||
return back()->with('success', 'Data warga berhasil disetujui.');
|
||||
})->name('data-warga.setujui');
|
||||
|
||||
Route::post('/data-warga/{id}/tolak', function (Request $request, $id) {
|
||||
if ($warga->tracking_status !== 'terkirim') {
|
||||
return back()->with('error', 'Data ini belum bisa masuk tahap validasi.');
|
||||
}
|
||||
|
||||
$warga->update([
|
||||
'tracking_status' => 'sedang_validasi',
|
||||
'status_verifikasi' => 'pending',
|
||||
]);
|
||||
|
||||
return back()->with('success', 'Proses validasi data telah dimulai.');
|
||||
})->name('data-warga.mulai-validasi');
|
||||
|
||||
// KIRIM HASIL VALIDASI KE RT
|
||||
Route::post('/data-warga/{id}/selesai-validasi', function (Request $request, $id) {
|
||||
$user = $request->user();
|
||||
if (!$user || $user->role !== 'admin') {
|
||||
abort(403, 'Akses ditolak: hanya Admin.');
|
||||
}
|
||||
|
||||
$warga = CalonPenerima::findOrFail($id);
|
||||
$warga->update(['status_verifikasi' => 'ditolak']);
|
||||
return back()->with('success', 'Data warga berhasil ditolak.');
|
||||
})->name('data-warga.tolak');
|
||||
|
||||
if ($warga->tracking_status !== 'sedang_validasi') {
|
||||
return back()->with('error', 'Data ini belum berada pada tahap sedang divalidasi.');
|
||||
}
|
||||
|
||||
if (!in_array($warga->status_verifikasi, ['disetujui', 'ditolak'])) {
|
||||
return back()->with('error', 'Hasil akhir belum ditetapkan. Silakan proses filterisasi terlebih dahulu.');
|
||||
}
|
||||
|
||||
$warga->update([
|
||||
'tracking_status' => 'selesai',
|
||||
]);
|
||||
|
||||
return back()->with('success', 'Hasil validasi berhasil dikirim ke RT.');
|
||||
})->name('data-warga.selesai-validasi');
|
||||
|
||||
// DATA AKUN (AdminController)
|
||||
Route::get('/data-akun', [AdminController::class, 'dataAkun'])->name('data-akun');
|
||||
Route::post('/data-akun/{id}/role', [AdminController::class, 'ubahRole'])->name('data-akun.role');
|
||||
Route::post('/data-akun/{id}/toggle-aktif', [AdminController::class, 'toggleAktif'])->name('data-akun.toggle-aktif');
|
||||
Route::post('/data-akun/{id}/hapus', [AdminController::class, 'hapusUser'])->name('data-akun.hapus');
|
||||
Route::delete('/data-akun/{id}/hapus', [AdminController::class, 'hapusUser'])->name('data-akun.hapus');
|
||||
|
||||
// Placeholder
|
||||
// FILTERISASI
|
||||
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');
|
||||
|
||||
// LAPORAN
|
||||
Route::get('/laporan', fn () => view('admin.laporan'))->name('laporan');
|
||||
});
|
||||
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| RT ROUTES (RT ONLY)
|
||||
|
|
@ -192,23 +221,14 @@
|
|||
->group(function () {
|
||||
|
||||
// DASHBOARD RT
|
||||
Route::get('/dashboard', [RtDashboardController::class, 'index'])
|
||||
->name('dashboard');
|
||||
Route::get('/dashboard', [RtDashboardController::class, 'index'])->name('dashboard');
|
||||
|
||||
// Resource RT
|
||||
// AJUKAN DATA KE ADMIN
|
||||
Route::patch('/calon-penerima/{calonPenerima}/ajukan', [RtCalonPenerimaController::class, 'ajukan'])
|
||||
->name('calon-penerima.ajukan');
|
||||
|
||||
// RESOURCE RT
|
||||
Route::resource('calon-penerima', RtCalonPenerimaController::class);
|
||||
});
|
||||
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| PROFILE
|
||||
|--------------------------------------------------------------------------
|
||||
*/
|
||||
Route::middleware('auth')->group(function () {
|
||||
Route::get('/profile', [UserProfileController::class, 'edit'])->name('profile.edit');
|
||||
Route::patch('/profile', [UserProfileController::class, 'update'])->name('profile.update');
|
||||
Route::delete('/profile', [UserProfileController::class, 'destroy'])->name('profile.destroy');
|
||||
});
|
||||
|
||||
require __DIR__ . '/auth.php';
|
||||
Loading…
Reference in New Issue