perbaikan semuanya dan conncting hosting
This commit is contained in:
parent
89f2f3dd76
commit
85181d866b
|
|
@ -0,0 +1,21 @@
|
|||
<IfModule mod_rewrite.c>
|
||||
<IfModule mod_negotiation.c>
|
||||
Options -MultiViews -Indexes
|
||||
</IfModule>
|
||||
|
||||
RewriteEngine On
|
||||
|
||||
RewriteCond %{HTTP:Authorization} .
|
||||
RewriteRule .* - [E=HTTP_AUTHORIZATION:%{HTTP:Authorization}]
|
||||
|
||||
RewriteCond %{HTTP:x-xsrf-token} .
|
||||
RewriteRule .* - [E=HTTP_X_XSRF_TOKEN:%{HTTP:X-XSRF-Token}]
|
||||
|
||||
RewriteCond %{REQUEST_FILENAME} !-d
|
||||
RewriteCond %{REQUEST_URI} (.+)/$
|
||||
RewriteRule ^ %1 [L,R=301]
|
||||
|
||||
RewriteCond %{REQUEST_FILENAME} !-d
|
||||
RewriteCond %{REQUEST_FILENAME} !-f
|
||||
RewriteRule ^ index.php [L]
|
||||
</IfModule>
|
||||
|
|
@ -91,14 +91,12 @@ public function tetapkan(Request $request)
|
|||
$topIds = $allCandidates->take($kuota)->pluck('id')->all();
|
||||
$allIds = $allCandidates->pluck('id')->all();
|
||||
|
||||
// Top kuota = disetujui
|
||||
if (!empty($topIds)) {
|
||||
CalonPenerima::whereIn('id', $topIds)->update([
|
||||
'status_verifikasi' => 'disetujui',
|
||||
]);
|
||||
}
|
||||
|
||||
// Sisanya = ditolak
|
||||
$notPickedIds = array_diff($allIds, $topIds);
|
||||
|
||||
if (!empty($notPickedIds)) {
|
||||
|
|
@ -107,7 +105,6 @@ public function tetapkan(Request $request)
|
|||
]);
|
||||
}
|
||||
|
||||
// Ambil ulang data final setelah status diperbarui
|
||||
$finalCandidates = CalonPenerima::query()
|
||||
->with(['rt.dusun', 'prediksiKelayakan'])
|
||||
->whereIn('id', $allIds)
|
||||
|
|
@ -118,7 +115,7 @@ public function tetapkan(Request $request)
|
|||
|
||||
PenerimaFinal::updateOrCreate(
|
||||
[
|
||||
'nik' => $candidate->nik,
|
||||
'nik' => $candidate->nik,
|
||||
'periode_bantuan' => '2026 Triwulan 1',
|
||||
],
|
||||
[
|
||||
|
|
@ -131,6 +128,9 @@ public function tetapkan(Request $request)
|
|||
'penghasilan' => $candidate->penghasilan ?? 0,
|
||||
'jumlah_tanggungan' => $candidate->jumlah_tanggungan ?? 0,
|
||||
'aset_kepemilikan' => $candidate->aset_kepemilikan ?? '-',
|
||||
'kondisi_rumah' => $candidate->kondisi_rumah ?? 'Tidak Diketahui',
|
||||
'meteran_listrik' => $candidate->meteran_listrik ?? 'Tidak Diketahui',
|
||||
'sumber_air' => $candidate->sumber_air ?? 'Tidak Diketahui',
|
||||
'bantuan_lain' => $candidate->bantuan_lain ?? 'tidak',
|
||||
'usia' => $candidate->usia ?? 0,
|
||||
'probability' => $probability,
|
||||
|
|
|
|||
|
|
@ -0,0 +1,83 @@
|
|||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Auth;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Models\User;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Hash;
|
||||
use Illuminate\Validation\Rules\Password;
|
||||
|
||||
class ManualPasswordResetController extends Controller
|
||||
{
|
||||
/**
|
||||
* Tampilkan halaman reset password manual.
|
||||
*/
|
||||
public function showForm()
|
||||
{
|
||||
return view('auth.forgot-password');
|
||||
}
|
||||
|
||||
/**
|
||||
* AJAX: cek apakah email terdaftar di sistem.
|
||||
* POST /password/manual/check
|
||||
*/
|
||||
public function checkEmail(Request $request)
|
||||
{
|
||||
try {
|
||||
$validated = $request->validate([
|
||||
'email' => ['required', 'email'],
|
||||
]);
|
||||
|
||||
$found = User::where('email', $validated['email'])->exists();
|
||||
|
||||
return response()->json(['found' => $found]);
|
||||
|
||||
} catch (\Illuminate\Validation\ValidationException $e) {
|
||||
return response()->json([
|
||||
'found' => false,
|
||||
'message' => collect($e->errors())->flatten()->first(),
|
||||
], 422);
|
||||
|
||||
} catch (\Exception $e) {
|
||||
return response()->json([
|
||||
'found' => false,
|
||||
'message' => 'Terjadi kesalahan server: ' . $e->getMessage(),
|
||||
], 500);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* AJAX: update password berdasarkan email yang sudah diverifikasi.
|
||||
* POST /password/manual/update
|
||||
*/
|
||||
public function updatePassword(Request $request)
|
||||
{
|
||||
try {
|
||||
$validated = $request->validate([
|
||||
'email' => ['required', 'email', 'exists:users,email'],
|
||||
'password' => ['required', 'confirmed', Password::min(8)],
|
||||
'password_confirmation' => ['required'],
|
||||
]);
|
||||
|
||||
$user = User::where('email', $validated['email'])->firstOrFail();
|
||||
$user->update([
|
||||
'password' => Hash::make($validated['password']),
|
||||
]);
|
||||
|
||||
return response()->json(['success' => true]);
|
||||
|
||||
} catch (\Illuminate\Validation\ValidationException $e) {
|
||||
return response()->json([
|
||||
'success' => false,
|
||||
'message' => collect($e->errors())->flatten()->first(),
|
||||
], 422);
|
||||
|
||||
} catch (\Exception $e) {
|
||||
return response()->json([
|
||||
'success' => false,
|
||||
'message' => 'Terjadi kesalahan server: ' . $e->getMessage(),
|
||||
], 500);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -6,6 +6,7 @@
|
|||
use Illuminate\Http\RedirectResponse;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Password;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
use Illuminate\View\View;
|
||||
|
||||
class PasswordResetLinkController extends Controller
|
||||
|
|
@ -29,6 +30,10 @@ public function store(Request $request): RedirectResponse
|
|||
'email' => ['required', 'email'],
|
||||
]);
|
||||
|
||||
Log::info('TES RESET PASSWORD MASUK', [
|
||||
'email' => $request->email,
|
||||
]);
|
||||
|
||||
// We will send the password reset link to this user. Once we have attempted
|
||||
// to send the link, we will examine the response then see the message we
|
||||
// need to show to the user. Finally, we'll send out a proper response.
|
||||
|
|
@ -41,4 +46,4 @@ public function store(Request $request): RedirectResponse
|
|||
: back()->withInput($request->only('email'))
|
||||
->withErrors(['email' => __($status)]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -10,6 +10,7 @@
|
|||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Illuminate\Support\Facades\Storage;
|
||||
|
||||
class CalonPenerimaController extends Controller
|
||||
{
|
||||
|
|
@ -34,15 +35,12 @@ 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();
|
||||
|
||||
// fallback kalau data RT user belum ada (harusnya jarang terjadi)
|
||||
if ($rts->isEmpty()) {
|
||||
$rts = RT::with('dusun')->get();
|
||||
}
|
||||
|
|
@ -60,75 +58,107 @@ public function store(Request $request)
|
|||
}
|
||||
|
||||
$validated = $request->validate([
|
||||
// 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' => 'nullable|string|max:255', // akan kita set otomatis
|
||||
'pekerjaan' => 'required|string',
|
||||
'penghasilan' => 'required|numeric|min:0',
|
||||
'jumlah_tanggungan' => 'required|integer|min:0',
|
||||
'aset_kepemilikan' => 'required|string',
|
||||
'bantuan_lain' => 'required|in:ya,tidak',
|
||||
'usia' => 'required|integer|min:17|max:100',
|
||||
'status_perkawinan' => 'required|string',
|
||||
'rt_id' => 'nullable|exists:rts,id',
|
||||
'no_kk' => 'required|digits:16|unique:calon_penerimas,no_kk',
|
||||
'nik' => 'required|digits:16|unique:calon_penerimas,nik',
|
||||
'nama_lengkap' => 'required|string|max:255',
|
||||
'jenis_kelamin' => 'required|in:Laki-laki,Perempuan',
|
||||
'tempat_lahir' => 'required|string|max:255',
|
||||
'tanggal_lahir' => 'required|date',
|
||||
'alamat' => 'required|string',
|
||||
'desa' => 'nullable|string|max:255',
|
||||
'pekerjaan' => 'required|string',
|
||||
'penghasilan' => 'required|numeric|min:0',
|
||||
'jumlah_tanggungan' => 'required|integer|min:0',
|
||||
'aset_kepemilikan' => 'required|string',
|
||||
'bantuan_lain' => 'required|in:ya,tidak',
|
||||
'usia' => 'required|integer|min:17|max:100',
|
||||
'status_perkawinan' => 'required|string',
|
||||
'kondisi_rumah' => 'required|in:Layak,Sedang,Tidak Layak',
|
||||
'meteran_listrik' => 'required|in:450VA,900VA,1300VA+',
|
||||
'sumber_air' => 'required|in:PDAM,Sumur,Sungai',
|
||||
// Upload foto (opsional)
|
||||
'foto_rumah_depan' => 'nullable|image|max:2048',
|
||||
'foto_rumah_belakang' => 'nullable|image|max:2048',
|
||||
'foto_rumah_kanan' => 'nullable|image|max:2048',
|
||||
'foto_rumah_kiri' => 'nullable|image|max:2048',
|
||||
'foto_kk' => 'nullable|image|max:2048',
|
||||
'foto_ktp' => 'nullable|image|max:2048',
|
||||
'foto_rekening_listrik' => 'nullable|mimes:jpg,jpeg,png,pdf|max:2048',
|
||||
'foto_meteran_air' => 'nullable|image|max:2048',
|
||||
'dokumen_pendukung' => 'nullable|mimes:jpg,jpeg,png,pdf,doc,docx|max:5120',
|
||||
], [
|
||||
'nik.unique' => 'NIK sudah pernah diinput.',
|
||||
'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 {
|
||||
// Handle upload foto
|
||||
$fotoFields = [
|
||||
'foto_rumah_depan', 'foto_rumah_belakang', 'foto_rumah_kanan', 'foto_rumah_kiri',
|
||||
'foto_kk', 'foto_ktp', 'foto_rekening_listrik', 'foto_meteran_air', 'dokumen_pendukung',
|
||||
];
|
||||
foreach ($fotoFields as $field) {
|
||||
if ($request->hasFile($field)) {
|
||||
$validated[$field] = $request->file($field)->store('dokumen-warga', 'public');
|
||||
}
|
||||
}
|
||||
|
||||
$calonPenerima = CalonPenerima::create([
|
||||
'user_id' => $user->id,
|
||||
'rt_id' => $validated['rt_id'],
|
||||
'no_kk' => $validated['no_kk'],
|
||||
'nik' => $validated['nik'],
|
||||
'nama_lengkap' => $validated['nama_lengkap'],
|
||||
'jenis_kelamin' => $validated['jenis_kelamin'],
|
||||
'tempat_lahir' => $validated['tempat_lahir'],
|
||||
'tanggal_lahir' => $validated['tanggal_lahir'],
|
||||
'alamat' => $validated['alamat'],
|
||||
'desa' => $validated['desa'],
|
||||
'pekerjaan' => $validated['pekerjaan'],
|
||||
'penghasilan' => $validated['penghasilan'],
|
||||
'jumlah_tanggungan' => $validated['jumlah_tanggungan'],
|
||||
'aset_kepemilikan' => $validated['aset_kepemilikan'],
|
||||
'bantuan_lain' => $validated['bantuan_lain'],
|
||||
'usia' => $validated['usia'],
|
||||
'status_perkawinan' => $validated['status_perkawinan'],
|
||||
'status_verifikasi' => 'pending',
|
||||
'tracking_status' => 'draft',
|
||||
'user_id' => $user->id,
|
||||
'rt_id' => $validated['rt_id'],
|
||||
'no_kk' => $validated['no_kk'],
|
||||
'nik' => $validated['nik'],
|
||||
'nama_lengkap' => $validated['nama_lengkap'],
|
||||
'jenis_kelamin' => $validated['jenis_kelamin'],
|
||||
'tempat_lahir' => $validated['tempat_lahir'],
|
||||
'tanggal_lahir' => $validated['tanggal_lahir'],
|
||||
'alamat' => $validated['alamat'],
|
||||
'desa' => $validated['desa'],
|
||||
'pekerjaan' => $validated['pekerjaan'],
|
||||
'penghasilan' => $validated['penghasilan'],
|
||||
'jumlah_tanggungan' => $validated['jumlah_tanggungan'],
|
||||
'aset_kepemilikan' => $validated['aset_kepemilikan'],
|
||||
'kondisi_rumah' => $validated['kondisi_rumah'],
|
||||
'meteran_listrik' => $validated['meteran_listrik'],
|
||||
'sumber_air' => $validated['sumber_air'],
|
||||
'bantuan_lain' => $validated['bantuan_lain'],
|
||||
'usia' => $validated['usia'],
|
||||
'status_perkawinan' => $validated['status_perkawinan'],
|
||||
'status_verifikasi' => 'pending',
|
||||
'tracking_status' => 'draft',
|
||||
'foto_rumah_depan' => $validated['foto_rumah_depan'] ?? null,
|
||||
'foto_rumah_belakang' => $validated['foto_rumah_belakang'] ?? null,
|
||||
'foto_rumah_kanan' => $validated['foto_rumah_kanan'] ?? null,
|
||||
'foto_rumah_kiri' => $validated['foto_rumah_kiri'] ?? null,
|
||||
'foto_kk' => $validated['foto_kk'] ?? null,
|
||||
'foto_ktp' => $validated['foto_ktp'] ?? null,
|
||||
'foto_rekening_listrik' => $validated['foto_rekening_listrik'] ?? null,
|
||||
'foto_meteran_air' => $validated['foto_meteran_air'] ?? null,
|
||||
'dokumen_pendukung' => $validated['dokumen_pendukung'] ?? null,
|
||||
]);
|
||||
|
||||
// 🔮 ML Prediction
|
||||
// ML Prediction
|
||||
$predictionData = [
|
||||
'pekerjaan' => $validated['pekerjaan'],
|
||||
'penghasilan' => $validated['penghasilan'],
|
||||
'pekerjaan' => $validated['pekerjaan'],
|
||||
'penghasilan' => $validated['penghasilan'],
|
||||
'jumlah_tanggungan' => $validated['jumlah_tanggungan'],
|
||||
'aset_kepemilikan' => $validated['aset_kepemilikan'],
|
||||
'bantuan_lain' => $validated['bantuan_lain'],
|
||||
'usia' => $validated['usia'],
|
||||
'aset_kepemilikan' => $validated['aset_kepemilikan'],
|
||||
'bantuan_lain' => $validated['bantuan_lain'],
|
||||
'usia' => $validated['usia'],
|
||||
'kondisi_rumah' => $validated['kondisi_rumah'],
|
||||
'meteran_listrik' => $validated['meteran_listrik'],
|
||||
'sumber_air' => $validated['sumber_air'],
|
||||
];
|
||||
|
||||
$prediction = $this->mlService->getPrediction($predictionData);
|
||||
|
|
@ -136,8 +166,11 @@ public function store(Request $request)
|
|||
if ($prediction) {
|
||||
PrediksiKelayakan::create([
|
||||
'calon_penerima_id' => $calonPenerima->id,
|
||||
'probability' => $prediction['probability'],
|
||||
'recommendation' => $prediction['recommendation'],
|
||||
'probability' => $prediction['probability'],
|
||||
'recommendation' => $prediction['recommendation'],
|
||||
'kondisi_rumah' => $validated['kondisi_rumah'],
|
||||
'meteran_listrik' => $validated['meteran_listrik'],
|
||||
'sumber_air' => $validated['sumber_air'],
|
||||
]);
|
||||
}
|
||||
|
||||
|
|
@ -147,10 +180,7 @@ public function store(Request $request)
|
|||
->with('success', 'Data calon penerima berhasil ditambahkan!');
|
||||
} catch (\Exception $e) {
|
||||
DB::rollBack();
|
||||
|
||||
return back()
|
||||
->withInput()
|
||||
->with('error', 'Terjadi kesalahan: ' . $e->getMessage());
|
||||
return back()->withInput()->with('error', 'Terjadi kesalahan: ' . $e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -158,7 +188,6 @@ public function show(CalonPenerima $calonPenerima)
|
|||
{
|
||||
$user = Auth::user();
|
||||
|
||||
// 🔒 RT hanya boleh lihat data miliknya
|
||||
if ($calonPenerima->user_id !== $user->id) {
|
||||
abort(403, 'Unauthorized action.');
|
||||
}
|
||||
|
|
@ -174,7 +203,6 @@ public function edit(CalonPenerima $calonPenerima)
|
|||
{
|
||||
$user = Auth::user();
|
||||
|
||||
// 🔒 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.');
|
||||
}
|
||||
|
|
@ -186,7 +214,6 @@ public function update(Request $request, CalonPenerima $calonPenerima)
|
|||
{
|
||||
$user = Auth::user();
|
||||
|
||||
// 🔒 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.');
|
||||
}
|
||||
|
|
@ -197,34 +224,41 @@ public function update(Request $request, CalonPenerima $calonPenerima)
|
|||
}
|
||||
|
||||
$validated = $request->validate([
|
||||
'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' => 'nullable|string|max:255', // akan di-override
|
||||
'pekerjaan' => 'required|string',
|
||||
'penghasilan' => 'required|numeric|min:0',
|
||||
'jumlah_tanggungan' => 'required|integer|min:0',
|
||||
'aset_kepemilikan' => 'required|string',
|
||||
'bantuan_lain' => 'required|in:ya,tidak',
|
||||
'usia' => 'required|integer|min:17|max:100',
|
||||
'status_perkawinan' => 'required|string',
|
||||
'rt_id' => 'nullable|exists:rts,id',
|
||||
'no_kk' => 'required|digits:16|unique:calon_penerimas,no_kk,' . $calonPenerima->id,
|
||||
'nik' => 'required|digits:16|unique:calon_penerimas,nik,' . $calonPenerima->id,
|
||||
'nama_lengkap' => 'required|string|max:255',
|
||||
'jenis_kelamin' => 'required|in:Laki-laki,Perempuan',
|
||||
'tempat_lahir' => 'required|string|max:255',
|
||||
'tanggal_lahir' => 'required|date',
|
||||
'alamat' => 'required|string',
|
||||
'desa' => 'nullable|string|max:255',
|
||||
'pekerjaan' => 'required|string',
|
||||
'penghasilan' => 'required|numeric|min:0',
|
||||
'jumlah_tanggungan' => 'required|integer|min:0',
|
||||
'aset_kepemilikan' => 'required|string',
|
||||
'bantuan_lain' => 'required|in:ya,tidak',
|
||||
'usia' => 'required|integer|min:17|max:100',
|
||||
'status_perkawinan' => 'required|string',
|
||||
'kondisi_rumah' => 'required|in:Layak,Sedang,Tidak Layak',
|
||||
'meteran_listrik' => 'required|in:450VA,900VA,1300VA+',
|
||||
'sumber_air' => 'required|in:PDAM,Sumur,Sungai',
|
||||
'foto_rumah_depan' => 'nullable|image|max:2048',
|
||||
'foto_rumah_belakang' => 'nullable|image|max:2048',
|
||||
'foto_rumah_kanan' => 'nullable|image|max:2048',
|
||||
'foto_rumah_kiri' => 'nullable|image|max:2048',
|
||||
'foto_kk' => 'nullable|image|max:2048',
|
||||
'foto_ktp' => 'nullable|image|max:2048',
|
||||
'foto_rekening_listrik' => 'nullable|mimes:jpg,jpeg,png,pdf|max:2048',
|
||||
'foto_meteran_air' => 'nullable|image|max:2048',
|
||||
'dokumen_pendukung' => 'nullable|mimes:jpg,jpeg,png,pdf,doc,docx|max:5120',
|
||||
], [
|
||||
'nik.unique' => 'NIK sudah pernah diinput.',
|
||||
'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;
|
||||
|
|
@ -234,29 +268,45 @@ public function update(Request $request, CalonPenerima $calonPenerima)
|
|||
|
||||
DB::beginTransaction();
|
||||
try {
|
||||
// Handle upload foto — hapus lama kalau ada yang baru
|
||||
$fotoFields = [
|
||||
'foto_rumah_depan', 'foto_rumah_belakang', 'foto_rumah_kanan', 'foto_rumah_kiri',
|
||||
'foto_kk', 'foto_ktp', 'foto_rekening_listrik', 'foto_meteran_air', 'dokumen_pendukung',
|
||||
];
|
||||
foreach ($fotoFields as $field) {
|
||||
if ($request->hasFile($field)) {
|
||||
if ($calonPenerima->$field) {
|
||||
Storage::disk('public')->delete($calonPenerima->$field);
|
||||
}
|
||||
$validated[$field] = $request->file($field)->store('dokumen-warga', 'public');
|
||||
}
|
||||
}
|
||||
|
||||
$calonPenerima->update($validated);
|
||||
|
||||
// (Opsional) Kalau kamu mau: update ulang prediksi ML saat data diupdate
|
||||
// Kalau mau aktif, uncomment ini:
|
||||
|
||||
/*
|
||||
// Uncomment kalau mau update ulang prediksi ML saat edit
|
||||
$predictionData = [
|
||||
'pekerjaan' => $validated['pekerjaan'],
|
||||
'penghasilan' => $validated['penghasilan'],
|
||||
'pekerjaan' => $validated['pekerjaan'],
|
||||
'penghasilan' => $validated['penghasilan'],
|
||||
'jumlah_tanggungan' => $validated['jumlah_tanggungan'],
|
||||
'aset_kepemilikan' => $validated['aset_kepemilikan'],
|
||||
'bantuan_lain' => $validated['bantuan_lain'],
|
||||
'usia' => $validated['usia'],
|
||||
'aset_kepemilikan' => $validated['aset_kepemilikan'],
|
||||
'bantuan_lain' => $validated['bantuan_lain'],
|
||||
'usia' => $validated['usia'],
|
||||
'kondisi_rumah' => $validated['kondisi_rumah'],
|
||||
'meteran_listrik' => $validated['meteran_listrik'],
|
||||
'sumber_air' => $validated['sumber_air'],
|
||||
];
|
||||
|
||||
$prediction = $this->mlService->getPrediction($predictionData);
|
||||
|
||||
if ($prediction) {
|
||||
PrediksiKelayakan::updateOrCreate(
|
||||
['calon_penerima_id' => $calonPenerima->id],
|
||||
[
|
||||
'probability' => $prediction['probability'],
|
||||
'probability' => $prediction['probability'],
|
||||
'recommendation' => $prediction['recommendation'],
|
||||
'kondisi_rumah' => $validated['kondisi_rumah'],
|
||||
'meteran_listrik'=> $validated['meteran_listrik'],
|
||||
'sumber_air' => $validated['sumber_air'],
|
||||
]
|
||||
);
|
||||
}
|
||||
|
|
@ -268,10 +318,7 @@ public function update(Request $request, CalonPenerima $calonPenerima)
|
|||
->with('success', 'Data calon penerima berhasil diupdate!');
|
||||
} catch (\Exception $e) {
|
||||
DB::rollBack();
|
||||
|
||||
return back()
|
||||
->withInput()
|
||||
->with('error', 'Terjadi kesalahan: ' . $e->getMessage());
|
||||
return back()->withInput()->with('error', 'Terjadi kesalahan: ' . $e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -288,9 +335,7 @@ public function ajukan(CalonPenerima $calonPenerima)
|
|||
->with('error', 'Data ini sudah diajukan dan tidak dapat diajukan lagi.');
|
||||
}
|
||||
|
||||
$calonPenerima->update([
|
||||
'tracking_status' => 'terkirim',
|
||||
]);
|
||||
$calonPenerima->update(['tracking_status' => 'terkirim']);
|
||||
|
||||
return redirect()->route('rt.calon-penerima.index')
|
||||
->with('success', 'Data berhasil diajukan ke admin kelurahan.');
|
||||
|
|
@ -300,11 +345,21 @@ public function destroy(CalonPenerima $calonPenerima)
|
|||
{
|
||||
$user = Auth::user();
|
||||
|
||||
// 🔒 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.');
|
||||
}
|
||||
|
||||
// Hapus foto dari storage
|
||||
$fotoFields = [
|
||||
'foto_rumah_depan', 'foto_rumah_belakang', 'foto_rumah_kanan', 'foto_rumah_kiri',
|
||||
'foto_kk', 'foto_ktp', 'foto_rekening_listrik', 'foto_meteran_air', 'dokumen_pendukung',
|
||||
];
|
||||
foreach ($fotoFields as $field) {
|
||||
if ($calonPenerima->$field) {
|
||||
Storage::disk('public')->delete($calonPenerima->$field);
|
||||
}
|
||||
}
|
||||
|
||||
$calonPenerima->delete();
|
||||
|
||||
return redirect()->route('rt.calon-penerima.index')
|
||||
|
|
@ -316,7 +371,6 @@ 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'])) {
|
||||
|
|
@ -325,7 +379,6 @@ private function getPredictionExplanation($calonPenerima): array
|
|||
$negative[] = 'Memiliki pekerjaan yang relatif lebih stabil';
|
||||
}
|
||||
|
||||
// PENGHASILAN
|
||||
if ($calonPenerima->penghasilan <= 1000000) {
|
||||
$positive[] = 'Penghasilan rendah';
|
||||
} elseif ($calonPenerima->penghasilan <= 2000000) {
|
||||
|
|
@ -334,7 +387,6 @@ private function getPredictionExplanation($calonPenerima): array
|
|||
$negative[] = 'Penghasilan relatif lebih tinggi';
|
||||
}
|
||||
|
||||
// JUMLAH TANGGUNGAN
|
||||
if ($calonPenerima->jumlah_tanggungan >= 4) {
|
||||
$positive[] = 'Jumlah tanggungan banyak';
|
||||
} elseif ($calonPenerima->jumlah_tanggungan >= 2) {
|
||||
|
|
@ -343,45 +395,54 @@ private function getPredictionExplanation($calonPenerima): array
|
|||
$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 (in_array($aset, ['tidak ada', '-', 'tidak punya'])) $positive[] = 'Tidak memiliki aset berarti';
|
||||
|
||||
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
|
||||
// Parameter baru
|
||||
if (strtolower($calonPenerima->kondisi_rumah ?? '') === 'tidak layak') {
|
||||
$positive[] = 'Kondisi rumah tidak layak huni';
|
||||
} elseif (strtolower($calonPenerima->kondisi_rumah ?? '') === 'sedang') {
|
||||
$positive[] = 'Kondisi rumah sedang';
|
||||
} else {
|
||||
$negative[] = 'Kondisi rumah layak';
|
||||
}
|
||||
|
||||
if (($calonPenerima->meteran_listrik ?? '') === '450VA') {
|
||||
$positive[] = 'Meteran listrik 450VA (daya rendah)';
|
||||
} elseif (($calonPenerima->meteran_listrik ?? '') === '900VA') {
|
||||
$positive[] = 'Meteran listrik 900VA';
|
||||
} else {
|
||||
$negative[] = 'Meteran listrik 1300VA ke atas';
|
||||
}
|
||||
|
||||
if (strtolower($calonPenerima->sumber_air ?? '') === 'sungai') {
|
||||
$positive[] = 'Sumber air dari sungai (kurang layak)';
|
||||
} elseif (strtolower($calonPenerima->sumber_air ?? '') === 'sumur') {
|
||||
$positive[] = 'Sumber air dari sumur';
|
||||
} else {
|
||||
$negative[] = 'Sumber air PDAM (layak)';
|
||||
}
|
||||
|
||||
$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.";
|
||||
$summary = "Nilai kelayakan {$probability}% diperoleh berdasarkan data pekerjaan, penghasilan, jumlah tanggungan, aset kepemilikan, bantuan lain, usia, kondisi rumah, meteran listrik, dan sumber air.";
|
||||
|
||||
if (count($positive) > 0 && count($negative) > 0) {
|
||||
$summary .= " Sistem menilai terdapat beberapa faktor yang mendukung kelayakan, namun ada juga faktor yang mengurangi nilai kelayakan.";
|
||||
|
|
@ -394,7 +455,7 @@ private function getPredictionExplanation($calonPenerima): array
|
|||
return [
|
||||
'positive' => $positive,
|
||||
'negative' => $negative,
|
||||
'summary' => $summary,
|
||||
'summary' => $summary,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
|
@ -4,7 +4,7 @@
|
|||
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Models\PenerimaFinal;
|
||||
use App\Models\Rt;
|
||||
use App\Models\RT;
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
class LaporanController extends Controller
|
||||
|
|
@ -17,7 +17,7 @@ public function index(Request $request)
|
|||
abort(403, 'Akses ditolak: hanya RT.');
|
||||
}
|
||||
|
||||
$rt = Rt::with('dusun')->find($user->rt_id);
|
||||
$rt = RT::with('dusun')->find($user->rt_id);
|
||||
|
||||
if (!$rt) {
|
||||
$laporans = collect();
|
||||
|
|
|
|||
|
|
@ -10,26 +10,37 @@ 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',
|
||||
'tracking_status', // status tracking proses
|
||||
'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',
|
||||
'kondisi_rumah',
|
||||
'meteran_listrik',
|
||||
'sumber_air',
|
||||
'bantuan_lain',
|
||||
'usia',
|
||||
'status_perkawinan',
|
||||
'status_verifikasi',
|
||||
'catatan_admin',
|
||||
'foto_rumah_depan',
|
||||
'foto_rumah_belakang',
|
||||
'foto_rumah_kanan',
|
||||
'foto_rumah_kiri',
|
||||
'foto_kk',
|
||||
'foto_ktp',
|
||||
'foto_rekening_listrik',
|
||||
'foto_meteran_air',
|
||||
'dokumen_pendukung',
|
||||
];
|
||||
|
||||
protected $casts = [
|
||||
|
|
|
|||
|
|
@ -20,6 +20,9 @@ class PenerimaFinal extends Model
|
|||
'penghasilan',
|
||||
'jumlah_tanggungan',
|
||||
'aset_kepemilikan',
|
||||
'kondisi_rumah',
|
||||
'meteran_listrik',
|
||||
'sumber_air',
|
||||
'bantuan_lain',
|
||||
'usia',
|
||||
'probability',
|
||||
|
|
@ -31,10 +34,10 @@ class PenerimaFinal extends Model
|
|||
|
||||
protected $casts = [
|
||||
'tanggal_penetapan' => 'date',
|
||||
'jumlah_bantuan' => 'decimal:2',
|
||||
'probability' => 'decimal:4',
|
||||
'penghasilan' => 'integer',
|
||||
'jumlah_bantuan' => 'decimal:2',
|
||||
'probability' => 'decimal:4',
|
||||
'penghasilan' => 'integer',
|
||||
'jumlah_tanggungan' => 'integer',
|
||||
'usia' => 'integer',
|
||||
'usia' => 'integer',
|
||||
];
|
||||
}
|
||||
|
|
@ -3,80 +3,49 @@
|
|||
namespace App\Services;
|
||||
|
||||
use Illuminate\Support\Facades\Log;
|
||||
use Illuminate\Support\Facades\Http;
|
||||
|
||||
class MLPredictionService
|
||||
{
|
||||
/**
|
||||
* Dapatkan prediksi dari script Python langsung
|
||||
*/
|
||||
public function getPrediction(array $data)
|
||||
{
|
||||
try {
|
||||
$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),
|
||||
'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),
|
||||
'kondisi_rumah' => $data['kondisi_rumah'] ?? 'Tidak Diketahui',
|
||||
'meteran_listrik' => $data['meteran_listrik'] ?? 'Tidak Diketahui',
|
||||
'sumber_air' => $data['sumber_air'] ?? 'Tidak Diketahui',
|
||||
];
|
||||
|
||||
$jsonPayload = json_encode($payload, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
|
||||
$apiUrl = env('ML_API_URL', 'https://ianvv.pythonanywhere.com') . '/predict';
|
||||
|
||||
$pythonPath = 'python';
|
||||
$scriptPath = base_path('ml/predict.py');
|
||||
$response = Http::timeout(30)->post($apiUrl, $payload);
|
||||
|
||||
$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;
|
||||
}
|
||||
|
||||
fwrite($pipes[0], $jsonPayload);
|
||||
fclose($pipes[0]);
|
||||
|
||||
$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,
|
||||
if (!$response->successful()) {
|
||||
Log::error('ML API gagal.', [
|
||||
'status' => $response->status(),
|
||||
'body' => $response->body(),
|
||||
]);
|
||||
return null;
|
||||
}
|
||||
|
||||
$result = json_decode($output, true);
|
||||
$result = $response->json();
|
||||
|
||||
if (!$result || isset($result['error'])) {
|
||||
Log::error('Hasil prediksi ML tidak valid.', [
|
||||
'output' => $output,
|
||||
'stderr' => $errorOutput,
|
||||
'output' => $response->body(),
|
||||
'decoded' => $result,
|
||||
]);
|
||||
return null;
|
||||
}
|
||||
|
||||
return [
|
||||
'probability' => $result['probability'] ?? 0,
|
||||
'probability' => $result['probability'] ?? 0,
|
||||
'recommendation' => $result['recommendation'] ?? 'Tidak Layak',
|
||||
];
|
||||
} catch (\Throwable $e) {
|
||||
|
|
@ -85,60 +54,30 @@ public function getPrediction(array $data)
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Cek apakah script ML bisa dijalankan
|
||||
*/
|
||||
public function healthCheck()
|
||||
{
|
||||
try {
|
||||
$pythonPath = 'python';
|
||||
$scriptPath = base_path('ml/predict.py');
|
||||
$apiUrl = env('ML_API_URL', 'https://ianvv.pythonanywhere.com') . '/predict';
|
||||
|
||||
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'],
|
||||
$testPayload = [
|
||||
'pekerjaan' => 'Buruh Harian',
|
||||
'penghasilan' => 500000,
|
||||
'jumlah_tanggungan' => 4,
|
||||
'aset_kepemilikan' => 'Rumah Sederhana',
|
||||
'bantuan_lain' => 'tidak',
|
||||
'usia' => 50,
|
||||
'kondisi_rumah' => 'Tidak Layak',
|
||||
'meteran_listrik' => '450VA',
|
||||
'sumber_air' => 'Sumur',
|
||||
];
|
||||
|
||||
$process = proc_open(
|
||||
$pythonPath . ' ' . escapeshellarg($scriptPath),
|
||||
$descriptorspec,
|
||||
$pipes,
|
||||
base_path()
|
||||
);
|
||||
$response = Http::timeout(10)->post($apiUrl, $testPayload);
|
||||
|
||||
if (!is_resource($process)) {
|
||||
if (!$response->successful()) {
|
||||
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);
|
||||
$result = $response->json();
|
||||
|
||||
return is_array($result) && isset($result['probability']) && isset($result['recommendation']);
|
||||
} catch (\Throwable $e) {
|
||||
|
|
@ -146,17 +85,12 @@ public function healthCheck()
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Prediksi untuk banyak data sekaligus
|
||||
*/
|
||||
public function getBatchPredictions(array $dataArray)
|
||||
{
|
||||
$results = [];
|
||||
|
||||
foreach ($dataArray as $item) {
|
||||
$results[] = $this->getPrediction($item);
|
||||
}
|
||||
|
||||
return $results;
|
||||
}
|
||||
}
|
||||
|
|
@ -77,6 +77,9 @@
|
|||
}
|
||||
},
|
||||
"config": {
|
||||
"platform": {
|
||||
"php": "8.2.0"
|
||||
},
|
||||
"optimize-autoloader": true,
|
||||
"preferred-install": "dist",
|
||||
"sort-packages": true,
|
||||
|
|
@ -87,4 +90,4 @@
|
|||
},
|
||||
"minimum-stability": "stable",
|
||||
"prefer-stable": true
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
|
|
@ -6,37 +6,44 @@
|
|||
|
||||
return new class extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*/
|
||||
public function up(): void
|
||||
public function up(): void
|
||||
{
|
||||
Schema::create('calon_penerimas', function (Blueprint $table) {
|
||||
$table->id(); // NO. URUT
|
||||
$table->id();
|
||||
$table->foreignId('user_id')->constrained('users')->onDelete('cascade');
|
||||
$table->foreignId('rt_id')->constrained('rts')->onDelete('cascade');
|
||||
|
||||
// Data Sesuai Kebutuhan
|
||||
$table->string('no_kk', 16); // NO. KK
|
||||
$table->string('nik', 16)->unique(); // NO. NIK KTP
|
||||
$table->string('nama_lengkap'); // NAMA LENGKAP
|
||||
$table->enum('jenis_kelamin', ['Laki-laki', 'Perempuan']); // JENIS KELAMIN
|
||||
$table->string('tempat_lahir'); // TEMPAT LAHIR
|
||||
$table->date('tanggal_lahir'); // TANGGAL LAHIR
|
||||
$table->text('alamat'); // ALAMAT
|
||||
$table->string('desa'); // DESA
|
||||
$table->string('pekerjaan'); // PEKERJAAN
|
||||
$table->decimal('penghasilan', 15, 2); // PENGHASILAN
|
||||
$table->integer('jumlah_tanggungan'); // JUMLAH TANGGUNGAN
|
||||
$table->string('aset_kepemilikan'); // ASET KEPEMILIKAN
|
||||
$table->enum('bantuan_lain', ['ya', 'tidak']); // BANTUAN LAIN
|
||||
$table->integer('usia'); // USIA
|
||||
$table->string('status_perkawinan'); // STATUS PERKAWINAN
|
||||
|
||||
// Untuk sistem (verifikasi admin)
|
||||
|
||||
$table->string('no_kk', 16);
|
||||
$table->string('nik', 16)->unique();
|
||||
$table->string('nama_lengkap');
|
||||
$table->enum('jenis_kelamin', ['Laki-laki', 'Perempuan']);
|
||||
$table->string('tempat_lahir');
|
||||
$table->date('tanggal_lahir');
|
||||
$table->text('alamat');
|
||||
$table->string('desa');
|
||||
$table->string('pekerjaan');
|
||||
$table->decimal('penghasilan', 15, 2);
|
||||
$table->integer('jumlah_tanggungan');
|
||||
$table->string('aset_kepemilikan');
|
||||
$table->string('kondisi_rumah')->default('Tidak Diketahui');
|
||||
$table->string('meteran_listrik')->default('Tidak Diketahui');
|
||||
$table->string('sumber_air')->default('Tidak Diketahui');
|
||||
$table->enum('bantuan_lain', ['ya', 'tidak']);
|
||||
$table->integer('usia');
|
||||
$table->string('status_perkawinan');
|
||||
$table->enum('status_verifikasi', ['pending', 'disetujui', 'ditolak'])->default('pending');
|
||||
$table->text('catatan_admin')->nullable();
|
||||
|
||||
|
||||
$table->string('foto_rumah_depan')->nullable();
|
||||
$table->string('foto_rumah_belakang')->nullable();
|
||||
$table->string('foto_rumah_kanan')->nullable();
|
||||
$table->string('foto_rumah_kiri')->nullable();
|
||||
$table->string('foto_kk')->nullable();
|
||||
$table->string('foto_ktp')->nullable();
|
||||
$table->string('foto_rekening_listrik')->nullable();
|
||||
$table->string('foto_meteran_air')->nullable();
|
||||
$table->string('dokumen_pendukung')->nullable();
|
||||
|
||||
$table->timestamps();
|
||||
});
|
||||
}
|
||||
|
|
@ -45,4 +52,4 @@ public function down(): void
|
|||
{
|
||||
Schema::dropIfExists('calon_penerimas');
|
||||
}
|
||||
};
|
||||
};
|
||||
|
|
@ -6,16 +6,16 @@
|
|||
|
||||
return new class extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*/
|
||||
public function up(): void
|
||||
public function up(): void
|
||||
{
|
||||
Schema::create('prediksi_kelayakans', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->foreignId('calon_penerima_id')->constrained('calon_penerimas')->onDelete('cascade');
|
||||
$table->decimal('probability', 5, 2); // Probabilitas kelayakan (0-100)
|
||||
$table->string('recommendation'); // Sangat Layak, Layak, Kurang Layak
|
||||
$table->decimal('probability', 5, 2);
|
||||
$table->string('recommendation');
|
||||
$table->string('kondisi_rumah')->default('Tidak Diketahui');
|
||||
$table->string('meteran_listrik')->default('Tidak Diketahui');
|
||||
$table->string('sumber_air')->default('Tidak Diketahui');
|
||||
$table->timestamps();
|
||||
});
|
||||
}
|
||||
|
|
@ -24,4 +24,4 @@ public function down(): void
|
|||
{
|
||||
Schema::dropIfExists('prediksi_kelayakans');
|
||||
}
|
||||
};
|
||||
};
|
||||
|
|
@ -0,0 +1,22 @@
|
|||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
public function up(): void
|
||||
{
|
||||
Schema::create('password_reset_tokens', function (Blueprint $table) {
|
||||
$table->string('email')->primary();
|
||||
$table->string('token');
|
||||
$table->timestamp('created_at')->nullable();
|
||||
});
|
||||
}
|
||||
|
||||
public function down(): void
|
||||
{
|
||||
Schema::dropIfExists('password_reset_tokens');
|
||||
}
|
||||
};
|
||||
|
|
@ -0,0 +1,21 @@
|
|||
[08-Apr-2026 16:30:23 UTC] PHP Fatal error: Composer detected issues in your platform: Your Composer dependencies require a PHP version ">= 8.3.0". You are running 8.2.30. in /home/tamyhost/public_html/E31232406/vendor/composer/platform_check.php on line 26
|
||||
[08-Apr-2026 17:03:37 UTC] PHP Fatal error: Composer detected issues in your platform: Your Composer dependencies require a PHP version ">= 8.3.0". You are running 8.2.30. in /home/tamyhost/public_html/E31232406/vendor/composer/platform_check.php on line 26
|
||||
[08-Apr-2026 17:06:51 UTC] PHP Fatal error: Composer detected issues in your platform: Your Composer dependencies require a PHP version ">= 8.3.0". You are running 8.2.30. in /home/tamyhost/public_html/E31232406/vendor/composer/platform_check.php on line 26
|
||||
[08-Apr-2026 17:42:39 UTC] PHP Warning: require(/home/tamyhost/public_html/E31232406/../vendor/autoload.php): Failed to open stream: No such file or directory in /home/tamyhost/public_html/E31232406/index.php on line 14
|
||||
[08-Apr-2026 17:42:39 UTC] PHP Warning: require(/home/tamyhost/public_html/E31232406/../vendor/autoload.php): Failed to open stream: No such file or directory in /home/tamyhost/public_html/E31232406/index.php on line 14
|
||||
[08-Apr-2026 17:42:39 UTC] PHP Fatal error: Uncaught Error: Failed opening required '/home/tamyhost/public_html/E31232406/../vendor/autoload.php' (include_path='.:/opt/cpanel/ea-php82/root/usr/share/pear') in /home/tamyhost/public_html/E31232406/index.php:14
|
||||
Stack trace:
|
||||
#0 {main}
|
||||
thrown in /home/tamyhost/public_html/E31232406/index.php on line 14
|
||||
[08-Apr-2026 17:42:42 UTC] PHP Warning: require(/home/tamyhost/public_html/E31232406/../vendor/autoload.php): Failed to open stream: No such file or directory in /home/tamyhost/public_html/E31232406/index.php on line 14
|
||||
[08-Apr-2026 17:42:42 UTC] PHP Warning: require(/home/tamyhost/public_html/E31232406/../vendor/autoload.php): Failed to open stream: No such file or directory in /home/tamyhost/public_html/E31232406/index.php on line 14
|
||||
[08-Apr-2026 17:42:42 UTC] PHP Fatal error: Uncaught Error: Failed opening required '/home/tamyhost/public_html/E31232406/../vendor/autoload.php' (include_path='.:/opt/cpanel/ea-php82/root/usr/share/pear') in /home/tamyhost/public_html/E31232406/index.php:14
|
||||
Stack trace:
|
||||
#0 {main}
|
||||
thrown in /home/tamyhost/public_html/E31232406/index.php on line 14
|
||||
[08-Apr-2026 17:42:43 UTC] PHP Warning: require(/home/tamyhost/public_html/E31232406/../vendor/autoload.php): Failed to open stream: No such file or directory in /home/tamyhost/public_html/E31232406/index.php on line 14
|
||||
[08-Apr-2026 17:42:43 UTC] PHP Warning: require(/home/tamyhost/public_html/E31232406/../vendor/autoload.php): Failed to open stream: No such file or directory in /home/tamyhost/public_html/E31232406/index.php on line 14
|
||||
[08-Apr-2026 17:42:43 UTC] PHP Fatal error: Uncaught Error: Failed opening required '/home/tamyhost/public_html/E31232406/../vendor/autoload.php' (include_path='.:/opt/cpanel/ea-php82/root/usr/share/pear') in /home/tamyhost/public_html/E31232406/index.php:14
|
||||
Stack trace:
|
||||
#0 {main}
|
||||
thrown in /home/tamyhost/public_html/E31232406/index.php on line 14
|
||||
|
|
@ -2,15 +2,19 @@ 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))
|
||||
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))
|
||||
kondisi_rumah = str(data.get("kondisi_rumah", "")).lower()
|
||||
meteran_listrik= str(data.get("meteran_listrik", "")).lower()
|
||||
sumber_air = str(data.get("sumber_air", "")).lower()
|
||||
|
||||
score = 0
|
||||
|
||||
# Parameter lama (tidak diubah)
|
||||
if pekerjaan in ["buruh", "tidak bekerja", "petani", "nelayan"]:
|
||||
score += 0.2
|
||||
|
||||
|
|
@ -31,22 +35,32 @@ def predict(data):
|
|||
if usia >= 60:
|
||||
score += 0.1
|
||||
|
||||
probability = max(0, min(score,1))
|
||||
# Parameter baru
|
||||
if kondisi_rumah == "tidak layak":
|
||||
score += 0.15
|
||||
elif kondisi_rumah == "sedang":
|
||||
score += 0.05
|
||||
|
||||
if meteran_listrik == "450va":
|
||||
score += 0.1
|
||||
elif meteran_listrik == "900va":
|
||||
score += 0.05
|
||||
|
||||
if sumber_air == "sungai":
|
||||
score += 0.1
|
||||
elif sumber_air == "sumur":
|
||||
score += 0.05
|
||||
|
||||
probability = max(0, min(score, 1))
|
||||
recommendation = "Layak" if probability >= 0.5 else "Tidak Layak"
|
||||
|
||||
return {
|
||||
"probability": round(probability,4),
|
||||
"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:
|
||||
|
|
@ -54,7 +68,6 @@ def read_input():
|
|||
except:
|
||||
pass
|
||||
|
||||
# Fallback ke argv
|
||||
if len(sys.argv) > 1:
|
||||
raw = sys.argv[1]
|
||||
return json.loads(raw)
|
||||
|
|
@ -63,7 +76,7 @@ def read_input():
|
|||
|
||||
if __name__ == "__main__":
|
||||
try:
|
||||
data = read_input()
|
||||
data = read_input()
|
||||
result = predict(data)
|
||||
print(json.dumps(result))
|
||||
except Exception as e:
|
||||
|
|
|
|||
Binary file not shown.
|
After Width: | Height: | Size: 223 KiB |
|
|
@ -1,16 +1,15 @@
|
|||
<x-app-layout>
|
||||
<x-slot name="header">
|
||||
<div style="display:flex;align-items:center;justify-content:space-between;gap:12px;flex-wrap:wrap;">
|
||||
<div style="display:flex;align-items:center;justify-content:space-between;gap:10px;flex-wrap:wrap;">
|
||||
<div>
|
||||
<h2 style="font-size:17px;font-weight:900;color:#0f172a;line-height:1.2;letter-spacing:-.02em;">Dashboard Kelurahan</h2>
|
||||
<p style="font-size:11px;color:#94a3b8;margin-top:2px;font-weight:500;">Ringkasan data kelayakan penerima BLT-DD seluruh dusun</p>
|
||||
<h2 style="font-size:16px;font-weight:900;color:#0f172a;letter-spacing:-.02em;">Dashboard Kelurahan</h2>
|
||||
<p style="font-size:11px;color:#94a3b8;margin-top:2px;font-weight:500;">Ringkasan data kelayakan penerima BLT-DD</p>
|
||||
</div>
|
||||
<div style="display:flex;align-items:center;gap:8px;">
|
||||
|
||||
{{-- ── BELL NOTIFIKASI ── --}}
|
||||
<div style="display:flex;align-items:center;gap:8px;flex-wrap:wrap;">
|
||||
{{-- BELL --}}
|
||||
<div style="position:relative;" id="notif-wrap">
|
||||
<button onclick="toggleNotif()"
|
||||
style="position:relative;width:36px;height:36px;background:#fff;border:1.5px solid #e2e8f0;border-radius:11px;display:flex;align-items:center;justify-content:center;cursor:pointer;transition:background .15s;box-shadow:0 1px 4px rgba(0,0,0,.05);">
|
||||
style="position:relative;width:36px;height:36px;background:#fff;border:1.5px solid #e2e8f0;border-radius:11px;display:flex;align-items:center;justify-content:center;cursor:pointer;box-shadow:0 1px 4px rgba(0,0,0,.05);">
|
||||
<svg width="16" height="16" fill="none" stroke="#64748b" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
|
||||
d="M15 17h5l-1.405-1.405A2.032 2.032 0 0118 14.158V11a6 6 0 00-9.33-4.976A6 6 0 006 11v3.159c0 .538-.214 1.055-.595 1.436L4 17h5m6 0v1a3 3 0 11-6 0v-1m6 0H9"/>
|
||||
|
|
@ -18,21 +17,20 @@
|
|||
<span id="notif-badge"
|
||||
style="display:none;position:absolute;top:-5px;right:-5px;min-width:17px;height:17px;border-radius:99px;background:#ef4444;color:#fff;font-size:9px;font-weight:800;align-items:center;justify-content:center;padding:0 3px;border:2px solid #fff;">0</span>
|
||||
</button>
|
||||
|
||||
<div id="notif-dropdown"
|
||||
style="display:none;position:absolute;top:calc(100% + 8px);right:0;width:310px;background:#fff;border-radius:18px;border:1.5px solid #f1f5f9;box-shadow:0 20px 56px rgba(0,0,0,.13);z-index:999;overflow:hidden;">
|
||||
style="display:none;position:absolute;top:calc(100% + 8px);right:0;width:min(310px,90vw);background:#fff;border-radius:18px;border:1.5px solid #f1f5f9;box-shadow:0 20px 56px rgba(0,0,0,.13);z-index:999;overflow:hidden;">
|
||||
<div style="padding:12px 15px;border-bottom:1px solid #f1f5f9;background:#fafbfc;display:flex;align-items:center;justify-content:space-between;">
|
||||
<span style="font-size:12px;font-weight:800;color:#0f172a;">Notifikasi</span>
|
||||
<button onclick="clearNotifs()" style="font-size:10.5px;color:#2563eb;cursor:pointer;font-weight:700;background:none;border:none;">Hapus semua</button>
|
||||
</div>
|
||||
<div id="notif-list" style="max-height:280px;overflow-y:auto;">
|
||||
<div style="padding:24px 16px;text-align:center;font-size:12px;color:#94a3b8;font-weight:500;">Tidak ada notifikasi</div>
|
||||
<div id="notif-list" style="max-height:260px;overflow-y:auto;">
|
||||
<div style="padding:24px 16px;text-align:center;font-size:12px;color:#94a3b8;">Tidak ada notifikasi</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{-- Clock --}}
|
||||
<div style="display:inline-flex;align-items:center;gap:6px;background:#fff;border:1.5px solid #e2e8f0;border-radius:11px;padding:7px 12px;font-size:11px;color:#64748b;font-weight:500;box-shadow:0 1px 4px rgba(0,0,0,.04);">
|
||||
{{-- CLOCK --}}
|
||||
<div style="display:inline-flex;align-items:center;gap:6px;background:#fff;border:1.5px solid #e2e8f0;border-radius:11px;padding:7px 12px;font-size:11px;color:#64748b;font-weight:500;">
|
||||
<svg width="13" height="13" fill="none" stroke="#3b82f6" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 8v4l3 3m6-3a9 9 0 11-18 0 9 9 0 0118 0z"/>
|
||||
</svg>
|
||||
|
|
@ -43,121 +41,112 @@
|
|||
</x-slot>
|
||||
|
||||
<style>
|
||||
/* ── Inline style approach — safe from Tailwind purge ── */
|
||||
.db-card{background:#fff;border-radius:20px;border:1.5px solid #f1f5f9;box-shadow:0 2px 8px rgba(0,0,0,.04),0 0 0 0 transparent;overflow:hidden;}
|
||||
/* ── SHARED ── */
|
||||
.db-card{background:#fff;border-radius:18px;border:1.5px solid #f1f5f9;box-shadow:0 2px 8px rgba(0,0,0,.04);overflow:hidden;}
|
||||
.sc-blob1{position:absolute;top:-16px;right:-16px;width:80px;height:80px;background:rgba(255,255,255,.12);border-radius:50%;pointer-events:none;}
|
||||
.sc-blob2{position:absolute;bottom:-20px;right:8px;width:56px;height:56px;background:rgba(255,255,255,.09);border-radius:50%;pointer-events:none;}
|
||||
|
||||
/* Stat cards */
|
||||
.sc-blob1{position:absolute;top:-16px;right:-16px;width:80px;height:80px;background:rgba(255,255,255,.12);border-radius:50%;}
|
||||
.sc-blob2{position:absolute;bottom:-20px;right:8px;width:56px;height:56px;background:rgba(255,255,255,.09);border-radius:50%;}
|
||||
|
||||
/* Top table */
|
||||
/* ── TABLE ── */
|
||||
.tt table{width:100%;border-collapse:collapse;}
|
||||
.tt thead tr{background:#f8fafc;border-bottom:1.5px solid #f1f5f9;}
|
||||
.tt thead th{padding:10px 14px;text-align:left;font-size:9.5px;font-weight:800;color:#94a3b8;text-transform:uppercase;letter-spacing:.08em;white-space:nowrap;}
|
||||
.tt thead th{padding:10px 12px;text-align:left;font-size:9.5px;font-weight:800;color:#94a3b8;white-space:nowrap;}
|
||||
.tt tbody tr{border-bottom:1px solid #f8fafc;transition:background .1s;}
|
||||
.tt tbody tr:hover{background:#f0f7ff;}
|
||||
.tt tbody tr:last-child{border-bottom:none;}
|
||||
.tt tbody td{padding:10px 14px;vertical-align:middle;}
|
||||
.tt tbody tr:hover{background:#f8fbff;}
|
||||
.tt tbody td{padding:10px 12px;font-size:12px;color:#374151;}
|
||||
|
||||
/* Rank badges */
|
||||
.rk{width:28px;height:28px;border-radius:50%;display:flex;align-items:center;justify-content:center;font-size:11px;font-weight:800;}
|
||||
.rk-1{background:#fef9c3;color:#b45309;}
|
||||
.rk-2{background:#f1f5f9;color:#475569;}
|
||||
.rk-3{background:#fff7ed;color:#c2410c;}
|
||||
.rk-n{background:#eff6ff;color:#2563eb;font-size:10px;}
|
||||
|
||||
/* Notif item */
|
||||
.ni{display:flex;align-items:flex-start;gap:10px;padding:11px 15px;border-bottom:1px solid #f8fafc;transition:background .1s;}
|
||||
.ni.unread{background:#eff6ff;}
|
||||
.ni-icon{width:28px;height:28px;border-radius:8px;display:flex;align-items:center;justify-content:center;flex-shrink:0;}
|
||||
.ni-icon svg{width:13px;height:13px;}
|
||||
.ni-txt{font-size:11.5px;color:#374151;font-weight:500;line-height:1.5;}
|
||||
/* notif items */
|
||||
.ni{display:flex;align-items:flex-start;gap:10px;padding:10px 14px;border-bottom:1px solid #f8fafc;}
|
||||
.ni.unread{background:#f0f7ff;}
|
||||
.ni-icon{width:30px;height:30px;border-radius:9px;display:flex;align-items:center;justify-content:center;flex-shrink:0;}
|
||||
.ni-icon svg{width:14px;height:14px;}
|
||||
.ni-txt{font-size:11.5px;font-weight:600;color:#1e293b;line-height:1.4;}
|
||||
.ni-time{font-size:10px;color:#94a3b8;margin-top:2px;}
|
||||
|
||||
/* ── RESPONSIVE ── */
|
||||
@media (max-width: 1024px) {
|
||||
.adm-stat-grid { grid-template-columns: repeat(2,1fr) !important; }
|
||||
.adm-chart-grid { grid-template-columns: 1fr !important; }
|
||||
}
|
||||
@media (max-width: 600px) {
|
||||
.adm-stat-grid { grid-template-columns: repeat(2,1fr) !important; }
|
||||
.adm-greeting { flex-direction: column !important; align-items: flex-start !important; }
|
||||
#admin-clock { display: none; }
|
||||
}
|
||||
@media (max-width: 400px) {
|
||||
.adm-stat-grid { grid-template-columns: 1fr !important; }
|
||||
}
|
||||
</style>
|
||||
|
||||
<script>
|
||||
/* ── Live clock (asli tidak diubah) ── */
|
||||
function updateAdminClock() {
|
||||
const now = new Date();
|
||||
const dateStr = now.toLocaleDateString('id-ID', { timeZone:'Asia/Jakarta', weekday:'long', day:'2-digit', month:'long', year:'numeric' });
|
||||
const timeStr = now.toLocaleTimeString('id-ID', { timeZone:'Asia/Jakarta', hour:'2-digit', minute:'2-digit', second:'2-digit', hour12:false });
|
||||
const el = document.getElementById('admin-clock');
|
||||
if (el) el.textContent = dateStr + ' — ' + timeStr + ' WIB';
|
||||
/* Clock */
|
||||
function updateAdminClock(){
|
||||
const now=new Date();
|
||||
const d=now.toLocaleDateString('id-ID',{timeZone:'Asia/Jakarta',weekday:'short',day:'2-digit',month:'short',year:'numeric'});
|
||||
const t=now.toLocaleTimeString('id-ID',{timeZone:'Asia/Jakarta',hour:'2-digit',minute:'2-digit',second:'2-digit',hour12:false});
|
||||
const el=document.getElementById('admin-clock');
|
||||
if(el) el.textContent=d+' '+t+' WIB';
|
||||
}
|
||||
updateAdminClock();
|
||||
setInterval(updateAdminClock, 1000);
|
||||
setInterval(updateAdminClock,1000);
|
||||
|
||||
/* ── Notifikasi realtime ── */
|
||||
const _nk = 'sbd_notifs_v1';
|
||||
const NS = {
|
||||
items: (() => { try { return JSON.parse(localStorage.getItem(_nk)||'[]'); } catch(e){ return []; } })(),
|
||||
save() { try { localStorage.setItem(_nk, JSON.stringify(this.items)); } catch(e){} },
|
||||
add(icon, color, text) {
|
||||
this.items.unshift({ id: Date.now(), icon, color, text, time: new Date().toLocaleTimeString('id-ID',{hour:'2-digit',minute:'2-digit'}), read: false });
|
||||
if (this.items.length > 30) this.items = this.items.slice(0, 30);
|
||||
this.save(); renderNotifs();
|
||||
/* Notifikasi */
|
||||
const _nk='sbd_notifs_v1';
|
||||
const NS={
|
||||
items:(()=>{try{return JSON.parse(localStorage.getItem(_nk)||'[]');}catch(e){return [];}}()),
|
||||
save(){try{localStorage.setItem(_nk,JSON.stringify(this.items));}catch(e){}},
|
||||
add(icon,color,text){
|
||||
this.items.unshift({id:Date.now(),icon,color,text,time:new Date().toLocaleTimeString('id-ID',{hour:'2-digit',minute:'2-digit'}),read:false});
|
||||
if(this.items.length>30)this.items=this.items.slice(0,30);
|
||||
this.save();renderNotifs();
|
||||
},
|
||||
markAllRead() { this.items.forEach(i => i.read = true); this.save(); },
|
||||
clearAll() { this.items = []; this.save(); },
|
||||
unread() { return this.items.filter(i => !i.read).length; }
|
||||
markAllRead(){this.items.forEach(i=>i.read=true);this.save();},
|
||||
clearAll(){this.items=[];this.save();},
|
||||
unread(){return this.items.filter(i=>!i.read).length;}
|
||||
};
|
||||
|
||||
function renderNotifs() {
|
||||
const list = document.getElementById('notif-list');
|
||||
const badge = document.getElementById('notif-badge');
|
||||
if (!list) return;
|
||||
const c = NS.unread();
|
||||
if (badge) { badge.textContent = c > 9 ? '9+' : c; badge.style.display = c > 0 ? 'flex' : 'none'; }
|
||||
if (!NS.items.length) {
|
||||
list.innerHTML = '<div style="padding:24px 16px;text-align:center;font-size:12px;color:#94a3b8;font-weight:500;">Tidak ada notifikasi</div>';
|
||||
return;
|
||||
}
|
||||
list.innerHTML = NS.items.map(n => `
|
||||
<div class="ni ${n.read?'':'unread'}">
|
||||
<div class="ni-icon" style="background:${n.color}18;">
|
||||
<svg fill="none" stroke="${n.color}" viewBox="0 0 24 24">${n.icon}</svg>
|
||||
</div>
|
||||
<div><div class="ni-txt">${n.text}</div><div class="ni-time">${n.time}</div></div>
|
||||
</div>`).join('');
|
||||
function renderNotifs(){
|
||||
const list=document.getElementById('notif-list');
|
||||
const badge=document.getElementById('notif-badge');
|
||||
if(!list)return;
|
||||
const c=NS.unread();
|
||||
if(badge){badge.textContent=c>9?'9+':c;badge.style.display=c>0?'flex':'none';}
|
||||
if(!NS.items.length){list.innerHTML='<div style="padding:24px 16px;text-align:center;font-size:12px;color:#94a3b8;">Tidak ada notifikasi</div>';return;}
|
||||
list.innerHTML=NS.items.map(n=>`<div class="ni ${n.read?'':'unread'}"><div class="ni-icon" style="background:${n.color}18;"><svg fill="none" stroke="${n.color}" viewBox="0 0 24 24">${n.icon}</svg></div><div><div class="ni-txt">${n.text}</div><div class="ni-time">${n.time}</div></div></div>`).join('');
|
||||
}
|
||||
|
||||
function toggleNotif() {
|
||||
const dd = document.getElementById('notif-dropdown');
|
||||
if (!dd) return;
|
||||
const opening = dd.style.display === 'none';
|
||||
dd.style.display = opening ? 'block' : 'none';
|
||||
if (opening) { NS.markAllRead(); renderNotifs(); }
|
||||
function toggleNotif(){
|
||||
const dd=document.getElementById('notif-dropdown');
|
||||
if(!dd)return;
|
||||
const opening=dd.style.display==='none';
|
||||
dd.style.display=opening?'block':'none';
|
||||
if(opening){NS.markAllRead();renderNotifs();}
|
||||
}
|
||||
function clearNotifs() { NS.clearAll(); renderNotifs(); }
|
||||
function clearNotifs(){NS.clearAll();renderNotifs();}
|
||||
|
||||
/* Poll 30s */
|
||||
let _lw = {{ $totalWarga ?? 0 }}, _lp = {{ $totalPending ?? 0 }}, _ln = {{ $totalPenerima ?? 0 }};
|
||||
async function pollStats() {
|
||||
try {
|
||||
const res = await fetch(window.location.href, { headers:{'X-Requested-With':'XMLHttpRequest','Accept':'application/json'} });
|
||||
if (!res.ok || !res.headers.get('content-type')?.includes('json')) return;
|
||||
const d = await res.json();
|
||||
if ((d.totalWarga??0) > _lw) { NS.add('<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"/>','#2563eb',`${d.totalWarga-_lw} data warga baru masuk ke sistem`); _lw=d.totalWarga; }
|
||||
if ((d.totalPending??0) > _lp) { NS.add('<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 8v4l3 3m6-3a9 9 0 11-18 0 9 9 0 0118 0z"/>','#f59e0b',`${d.totalPending-_lp} data baru menunggu verifikasi`); _lp=d.totalPending; }
|
||||
if ((d.totalPenerima??0) > _ln) { NS.add('<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"/>','#10b981',`${d.totalPenerima-_ln} warga baru ditetapkan sebagai penerima`); _ln=d.totalPenerima; }
|
||||
} catch(e) {}
|
||||
let _lw={{$totalWarga??0}},_lp={{$totalPending??0}},_ln={{$totalPenerima??0}};
|
||||
async function pollStats(){
|
||||
try{
|
||||
const res=await fetch(window.location.href,{headers:{'X-Requested-With':'XMLHttpRequest','Accept':'application/json'}});
|
||||
if(!res.ok||!res.headers.get('content-type')?.includes('json'))return;
|
||||
const d=await res.json();
|
||||
if((d.totalWarga??0)>_lw){NS.add('<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"/>','#2563eb',`${d.totalWarga-_lw} data warga baru`);_lw=d.totalWarga;}
|
||||
if((d.totalPending??0)>_lp){NS.add('<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 8v4l3 3m6-3a9 9 0 11-18 0 9 9 0 0118 0z"/>','#f59e0b',`${d.totalPending-_lp} data baru menunggu verifikasi`);_lp=d.totalPending;}
|
||||
if((d.totalPenerima??0)>_ln){NS.add('<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"/>','#10b981',`${d.totalPenerima-_ln} warga baru ditetapkan`);_ln=d.totalPenerima;}
|
||||
}catch(e){}
|
||||
}
|
||||
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
document.addEventListener('DOMContentLoaded',()=>{
|
||||
renderNotifs();
|
||||
setInterval(pollStats, 30000);
|
||||
document.addEventListener('click', e => {
|
||||
const w = document.getElementById('notif-wrap');
|
||||
if (w && !w.contains(e.target)) { const dd=document.getElementById('notif-dropdown'); if(dd) dd.style.display='none'; }
|
||||
setInterval(pollStats,30000);
|
||||
document.addEventListener('click',e=>{
|
||||
const w=document.getElementById('notif-wrap');
|
||||
if(w&&!w.contains(e.target)){const dd=document.getElementById('notif-dropdown');if(dd)dd.style.display='none';}
|
||||
});
|
||||
});
|
||||
</script>
|
||||
|
||||
<div style="display:flex;flex-direction:column;gap:16px;">
|
||||
<div style="display:flex;flex-direction:column;gap:14px;">
|
||||
|
||||
{{-- GREETING + NOTIFIKASI (asli tidak diubah, hanya ganti class ke inline style) --}}
|
||||
<div style="display:flex;flex-wrap:wrap;gap:10px;">
|
||||
<div style="display:flex;align-items:center;gap:12px;background:#fff;border:1.5px solid #f1f5f9;border-radius:18px;padding:14px 18px;box-shadow:0 1px 4px rgba(0,0,0,.04);flex:1;min-width:240px;">
|
||||
{{-- GREETING --}}
|
||||
<div class="adm-greeting" style="display:flex;flex-wrap:wrap;gap:10px;">
|
||||
<div style="display:flex;align-items:center;gap:12px;background:#fff;border:1.5px solid #f1f5f9;border-radius:16px;padding:14px 16px;box-shadow:0 1px 4px rgba(0,0,0,.04);flex:1;min-width:220px;">
|
||||
<div style="width:38px;height:38px;border-radius:50%;background:linear-gradient(135deg,#3b82f6,#2563eb);display:flex;align-items:center;justify-content:center;color:#fff;font-size:14px;font-weight:800;flex-shrink:0;box-shadow:0 4px 12px rgba(37,99,235,.28);">
|
||||
{{ strtoupper(substr(Auth::user()->name ?? 'A', 0, 1)) }}
|
||||
</div>
|
||||
|
|
@ -166,11 +155,9 @@ function clearNotifs() { NS.clearAll(); renderNotifs(); }
|
|||
<p style="font-size:11px;color:#94a3b8;margin-top:2px;font-weight:500;">Panel admin — kelola dan pantau seluruh data warga.</p>
|
||||
</div>
|
||||
</div>
|
||||
<div style="display:flex;align-items:center;gap:12px;background:#fffbeb;border:1.5px solid #fde68a;border-radius:18px;padding:14px 18px;box-shadow:0 1px 4px rgba(0,0,0,.04);">
|
||||
<div style="width:36px;height:36px;border-radius:50%;background:#f59e0b;display:flex;align-items:center;justify-content:center;flex-shrink:0;box-shadow:0 4px 12px rgba(245,158,11,.28);">
|
||||
<svg width="16" height="16" fill="none" stroke="#fff" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 17h5l-1.405-1.405A2.032 2.032 0 0118 14.158V11a6 6 0 00-9.33-4.976A6 6 0 006 11v3.159c0 .538-.214 1.055-.595 1.436L4 17h5m6 0v1a3 3 0 11-6 0v-1m6 0H9"/>
|
||||
</svg>
|
||||
<div style="display:flex;align-items:center;gap:12px;background:#fffbeb;border:1.5px solid #fde68a;border-radius:16px;padding:14px 16px;">
|
||||
<div style="width:36px;height:36px;border-radius:50%;background:#f59e0b;display:flex;align-items:center;justify-content:center;flex-shrink:0;">
|
||||
<svg width="16" height="16" fill="none" stroke="#fff" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 17h5l-1.405-1.405A2.032 2.032 0 0118 14.158V11a6 6 0 00-9.33-4.976A6 6 0 006 11v3.159c0 .538-.214 1.055-.595 1.436L4 17h5m6 0v1a3 3 0 11-6 0v-1m6 0H9"/></svg>
|
||||
</div>
|
||||
<div>
|
||||
<p style="font-size:12px;font-weight:700;color:#92400e;">{{ $totalPending ?? 0 }} data menunggu verifikasi</p>
|
||||
|
|
@ -179,303 +166,200 @@ function clearNotifs() { NS.clearAll(); renderNotifs(); }
|
|||
</div>
|
||||
</div>
|
||||
|
||||
{{-- STAT CARDS (asli tidak diubah, ganti ke inline style) --}}
|
||||
<div style="display:grid;grid-template-columns:repeat(4,1fr);gap:12px;">
|
||||
{{-- STAT CARDS --}}
|
||||
<div class="adm-stat-grid" style="display:grid;grid-template-columns:repeat(4,1fr);gap:12px;">
|
||||
|
||||
<div style="position:relative;background:linear-gradient(135deg,#2563eb,#1d4ed8);color:#fff;border-radius:18px;padding:18px;display:flex;align-items:center;justify-content:space-between;overflow:hidden;box-shadow:0 6px 20px rgba(37,99,235,.3);">
|
||||
@php
|
||||
$cards = [
|
||||
['label'=>'Total Warga','sub'=>'Dari semua dusun','val'=>$totalWarga??0,'grad'=>'#2563eb,#1d4ed8','shadow'=>'rgba(37,99,235,.3)','icon'=>'M17 20h5v-2a3 3 0 00-5.356-1.857M17 20H7m10 0v-2c0-.656-.126-1.283-.356-1.857M7 20H2v-2a3 3 0 015.356-1.857M7 20v-2c0-.656.126-1.283.356-1.857m0 0a5.002 5.002 0 019.288 0M15 7a3 3 0 11-6 0 3 3 0 016 0z','tc'=>'rgba(191,219,254,.9)'],
|
||||
['label'=>'Pending','sub'=>'Belum diproses','val'=>$totalPending??0,'grad'=>'#f59e0b,#d97706','shadow'=>'rgba(245,158,11,.3)','icon'=>'M12 8v4l3 3m6-3a9 9 0 11-18 0 9 9 0 0118 0z','tc'=>'rgba(254,243,199,.9)'],
|
||||
['label'=>'Diterima','sub'=>'Sudah ditetapkan','val'=>$totalPenerima??0,'grad'=>'#10b981,#059669','shadow'=>'rgba(16,185,129,.3)','icon'=>'M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z','tc'=>'rgba(167,243,208,.9)'],
|
||||
['label'=>'Ditolak','sub'=>'Tidak memenuhi syarat','val'=>$totalDitolak??0,'grad'=>'#f43f5e,#e11d48','shadow'=>'rgba(244,63,94,.3)','icon'=>'M10 14l2-2m0 0l2-2m-2 2l-2-2m2 2l2 2m7-2a9 9 0 11-18 0 9 9 0 0118 0z','tc'=>'rgba(254,205,211,.9)'],
|
||||
];
|
||||
@endphp
|
||||
|
||||
@foreach($cards as $c)
|
||||
<div style="position:relative;background:linear-gradient(135deg,{{ $c['grad'] }});color:#fff;border-radius:16px;padding:16px;display:flex;align-items:center;justify-content:space-between;overflow:hidden;box-shadow:0 6px 20px {{ $c['shadow'] }};">
|
||||
<div class="sc-blob1"></div><div class="sc-blob2"></div>
|
||||
<div style="position:relative;z-index:1;">
|
||||
<p style="font-size:10.5px;font-weight:600;color:rgba(191,219,254,.9);">Total Warga Masuk</p>
|
||||
<p style="font-size:32px;font-weight:900;margin-top:2px;line-height:1;letter-spacing:-.04em;">{{ $totalWarga ?? 0 }}</p>
|
||||
<p style="font-size:10px;color:rgba(191,219,254,.75);margin-top:3px;">Dari semua dusun</p>
|
||||
<p style="font-size:10px;font-weight:600;color:{{ $c['tc'] }};">{{ $c['label'] }}</p>
|
||||
<p style="font-size:28px;font-weight:900;margin-top:2px;line-height:1;letter-spacing:-.04em;">{{ $c['val'] }}</p>
|
||||
<p style="font-size:10px;color:rgba(255,255,255,.6);margin-top:2px;">{{ $c['sub'] }}</p>
|
||||
</div>
|
||||
<div style="position:relative;z-index:1;width:44px;height:44px;background:rgba(255,255,255,.18);border-radius:13px;display:flex;align-items:center;justify-content:center;flex-shrink:0;border:1px solid rgba(255,255,255,.2);">
|
||||
<svg width="22" height="22" fill="none" stroke="#fff" 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>
|
||||
</div>
|
||||
|
||||
<div style="position:relative;background:linear-gradient(135deg,#f59e0b,#d97706);color:#fff;border-radius:18px;padding:18px;display:flex;align-items:center;justify-content:space-between;overflow:hidden;box-shadow:0 6px 20px rgba(245,158,11,.3);">
|
||||
<div class="sc-blob1"></div><div class="sc-blob2"></div>
|
||||
<div style="position:relative;z-index:1;">
|
||||
<p style="font-size:10.5px;font-weight:600;color:rgba(254,243,199,.9);">Pending Verifikasi</p>
|
||||
<p style="font-size:32px;font-weight:900;margin-top:2px;line-height:1;letter-spacing:-.04em;">{{ $totalPending ?? 0 }}</p>
|
||||
<p style="font-size:10px;color:rgba(254,243,199,.75);margin-top:3px;">Belum diproses</p>
|
||||
</div>
|
||||
<div style="position:relative;z-index:1;width:44px;height:44px;background:rgba(255,255,255,.18);border-radius:13px;display:flex;align-items:center;justify-content:center;flex-shrink:0;border:1px solid rgba(255,255,255,.2);">
|
||||
<svg width="22" height="22" fill="none" stroke="#fff" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 8v4l3 3m6-3a9 9 0 11-18 0 9 9 0 0118 0z"/></svg>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div style="position:relative;background:linear-gradient(135deg,#10b981,#059669);color:#fff;border-radius:18px;padding:18px;display:flex;align-items:center;justify-content:space-between;overflow:hidden;box-shadow:0 6px 20px rgba(16,185,129,.3);">
|
||||
<div class="sc-blob1"></div><div class="sc-blob2"></div>
|
||||
<div style="position:relative;z-index:1;">
|
||||
<p style="font-size:10.5px;font-weight:600;color:rgba(167,243,208,.9);">Diterima / Ditetapkan</p>
|
||||
<p style="font-size:32px;font-weight:900;margin-top:2px;line-height:1;letter-spacing:-.04em;">{{ $totalPenerima ?? 0 }}</p>
|
||||
<p style="font-size:10px;color:rgba(167,243,208,.75);margin-top:3px;">Sudah ditetapkan</p>
|
||||
</div>
|
||||
<div style="position:relative;z-index:1;width:44px;height:44px;background:rgba(255,255,255,.18);border-radius:13px;display:flex;align-items:center;justify-content:center;flex-shrink:0;border:1px solid rgba(255,255,255,.2);">
|
||||
<svg width="22" height="22" fill="none" stroke="#fff" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z"/></svg>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div style="position:relative;background:linear-gradient(135deg,#f43f5e,#e11d48);color:#fff;border-radius:18px;padding:18px;display:flex;align-items:center;justify-content:space-between;overflow:hidden;box-shadow:0 6px 20px rgba(244,63,94,.3);">
|
||||
<div class="sc-blob1"></div><div class="sc-blob2"></div>
|
||||
<div style="position:relative;z-index:1;">
|
||||
<p style="font-size:10.5px;font-weight:600;color:rgba(254,205,211,.9);">Ditolak</p>
|
||||
<p style="font-size:32px;font-weight:900;margin-top:2px;line-height:1;letter-spacing:-.04em;">{{ $totalDitolak ?? 0 }}</p>
|
||||
<p style="font-size:10px;color:rgba(254,205,211,.75);margin-top:3px;">Tidak memenuhi syarat</p>
|
||||
</div>
|
||||
<div style="position:relative;z-index:1;width:44px;height:44px;background:rgba(255,255,255,.18);border-radius:13px;display:flex;align-items:center;justify-content:center;flex-shrink:0;border:1px solid rgba(255,255,255,.2);">
|
||||
<svg width="22" height="22" fill="none" stroke="#fff" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10 14l2-2m0 0l2-2m-2 2l-2-2m2 2l2 2m7-2a9 9 0 11-18 0 9 9 0 0118 0z"/></svg>
|
||||
<div style="position:relative;z-index:1;width:40px;height:40px;background:rgba(255,255,255,.18);border-radius:12px;display:flex;align-items:center;justify-content:center;flex-shrink:0;border:1px solid rgba(255,255,255,.2);">
|
||||
<svg width="20" height="20" fill="none" stroke="#fff" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="{{ $c['icon'] }}"/></svg>
|
||||
</div>
|
||||
</div>
|
||||
@endforeach
|
||||
|
||||
</div>
|
||||
|
||||
{{-- GRAFIK + DATA PER DUSUN (logika PHP asli 100% tidak diubah) --}}
|
||||
<div style="display:grid;grid-template-columns:2fr 1fr;gap:14px;">
|
||||
{{-- GRAFIK + DATA PER DUSUN --}}
|
||||
<div class="adm-chart-grid" style="display:grid;grid-template-columns:2fr 1fr;gap:14px;">
|
||||
|
||||
{{-- Grafik --}}
|
||||
<div class="db-card">
|
||||
<div style="padding:12px 18px;border-bottom:1px solid #f1f5f9;background:#fafbfc;display:flex;align-items:center;justify-content:space-between;">
|
||||
<div style="padding:12px 16px;border-bottom:1px solid #f1f5f9;background:#fafbfc;display:flex;align-items:center;justify-content:space-between;flex-wrap:wrap;gap:6px;">
|
||||
<div style="display:flex;align-items:center;gap:7px;">
|
||||
<div style="width:3px;height:16px;background:linear-gradient(180deg,#3b82f6,#2563eb);border-radius:4px;"></div>
|
||||
<h3 style="font-size:13px;font-weight:700;color:#1e293b;">Jumlah Warga per Dusun</h3>
|
||||
<div style="width:3px;height:15px;background:linear-gradient(180deg,#3b82f6,#2563eb);border-radius:4px;"></div>
|
||||
<h3 style="font-size:13px;font-weight:700;color:#1e293b;">Warga per Dusun</h3>
|
||||
</div>
|
||||
<span style="font-size:10.5px;color:#94a3b8;background:#fff;border:1px solid #f1f5f9;padding:2px 9px;border-radius:20px;font-weight:600;">
|
||||
{{ $wargaPerDusun->sum('total') }} total warga
|
||||
{{ $wargaPerDusun->sum('total') }} total
|
||||
</span>
|
||||
</div>
|
||||
<div style="padding:18px 20px;">
|
||||
<div style="padding:16px;overflow-x:auto;">
|
||||
@php
|
||||
$dusuns = $wargaPerDusun->values();
|
||||
$n = max($dusuns->count(), 1);
|
||||
$maxV = max((int)$dusuns->max('total'), 1);
|
||||
|
||||
$W = 560; $H = 210;
|
||||
$pL = 40; $pR = 20;
|
||||
$pT = 30; $pB = 45;
|
||||
$cW = $W - $pL - $pR;
|
||||
$cH = $H - $pT - $pB;
|
||||
|
||||
$step = max(1, (int)ceil($maxV / 4));
|
||||
$yTicks = range(0, $maxV + $step, $step);
|
||||
|
||||
$pts = [];
|
||||
foreach ($dusuns as $i => $d) {
|
||||
$x = $pL + ($n === 1 ? $cW / 2 : ($i / ($n - 1)) * $cW);
|
||||
$y = $pT + $cH - ($d->total / $maxV) * $cH;
|
||||
$pts[] = ['x' => round($x, 1), 'y' => round($y, 1), 'val' => $d->total, 'name' => $d->dusun];
|
||||
$dusuns=$wargaPerDusun->values();
|
||||
$n=max($dusuns->count(),1);
|
||||
$maxV=max((int)$dusuns->max('total'),1);
|
||||
$W=560;$H=200;$pL=40;$pR=20;$pT=28;$pB=44;
|
||||
$cW=$W-$pL-$pR;$cH=$H-$pT-$pB;
|
||||
$step=max(1,(int)ceil($maxV/4));
|
||||
$yTicks=range(0,$maxV+$step,$step);
|
||||
$pts=[];
|
||||
foreach($dusuns as $i=>$d){
|
||||
$x=$pL+($n===1?$cW/2:($i/($n-1))*$cW);
|
||||
$y=$pT+$cH-($d->total/$maxV)*$cH;
|
||||
$pts[]=['x'=>round($x,1),'y'=>round($y,1),'val'=>$d->total,'name'=>$d->dusun];
|
||||
}
|
||||
|
||||
$poly = implode(' ', array_map(fn($p) => "{$p['x']},{$p['y']}", $pts));
|
||||
$firstPt = $pts[0];
|
||||
$lastPt = $pts[count($pts)-1];
|
||||
$botY = $pT + $cH;
|
||||
$areaPath = "M {$firstPt['x']},{$firstPt['y']} "
|
||||
. implode(' ', array_map(fn($p) => "L {$p['x']},{$p['y']}", array_slice($pts, 1)))
|
||||
. " L {$lastPt['x']},{$botY} L {$firstPt['x']},{$botY} Z";
|
||||
$poly=implode(' ',array_map(fn($p)=>"{$p['x']},{$p['y']}",$pts));
|
||||
$firstPt=$pts[0];$lastPt=$pts[count($pts)-1];
|
||||
$botY=$pT+$cH;
|
||||
$areaPath="M {$firstPt['x']},{$firstPt['y']} ".implode(' ',array_map(fn($p)=>"L {$p['x']},{$p['y']}",array_slice($pts,1)))." L {$lastPt['x']},{$botY} L {$firstPt['x']},{$botY} Z";
|
||||
@endphp
|
||||
|
||||
<svg viewBox="0 0 {{ $W }} {{ $H }}" width="100%" xmlns="http://www.w3.org/2000/svg">
|
||||
<svg viewBox="0 0 {{ $W }} {{ $H }}" width="100%" style="min-width:260px;" xmlns="http://www.w3.org/2000/svg">
|
||||
<defs>
|
||||
<linearGradient id="fillGrad" x1="0" y1="0" x2="0" y2="1">
|
||||
<stop offset="0%" stop-color="#3b82f6" stop-opacity="0.18"/>
|
||||
<linearGradient id="fg" x1="0" y1="0" x2="0" y2="1">
|
||||
<stop offset="0%" stop-color="#3b82f6" stop-opacity=".18"/>
|
||||
<stop offset="100%" stop-color="#3b82f6" stop-opacity="0"/>
|
||||
</linearGradient>
|
||||
</defs>
|
||||
|
||||
@foreach($yTicks as $t)
|
||||
@if($t <= $maxV + $step)
|
||||
@php $gy = $pT + $cH - min($t, $maxV) / $maxV * $cH; @endphp
|
||||
<line x1="{{ $pL }}" y1="{{ $gy }}" x2="{{ $W - $pR }}" y2="{{ $gy }}" stroke="#f1f5f9" stroke-width="1"/>
|
||||
<text x="{{ $pL - 5 }}" y="{{ $gy + 4 }}" text-anchor="end" font-size="10" fill="#94a3b8">{{ $t }}</text>
|
||||
@if($t<=$maxV+$step)
|
||||
@php $gy=$pT+$cH-min($t,$maxV)/$maxV*$cH; @endphp
|
||||
<line x1="{{ $pL }}" y1="{{ $gy }}" x2="{{ $W-$pR }}" y2="{{ $gy }}" stroke="#f1f5f9" stroke-width="1"/>
|
||||
<text x="{{ $pL-5 }}" y="{{ $gy+4 }}" text-anchor="end" font-size="10" fill="#94a3b8">{{ $t }}</text>
|
||||
@endif
|
||||
@endforeach
|
||||
|
||||
<text x="10" y="{{ $pT + $cH / 2 }}" text-anchor="middle" font-size="9" fill="#94a3b8" transform="rotate(-90,10,{{ $pT + $cH / 2 }})">Jumlah Warga</text>
|
||||
<line x1="{{ $pL }}" y1="{{ $botY }}" x2="{{ $W - $pR }}" y2="{{ $botY }}" stroke="#cbd5e1" stroke-width="1.5"/>
|
||||
<polygon points="{{ $W - $pR }},{{ $botY - 4 }} {{ $W - $pR + 8 }},{{ $botY }} {{ $W - $pR }},{{ $botY + 4 }}" fill="#94a3b8"/>
|
||||
<line x1="{{ $pL }}" y1="{{ $botY }}" x2="{{ $W-$pR }}" y2="{{ $botY }}" stroke="#cbd5e1" stroke-width="1.5"/>
|
||||
<line x1="{{ $pL }}" y1="{{ $pT }}" x2="{{ $pL }}" y2="{{ $botY }}" stroke="#cbd5e1" stroke-width="1.5"/>
|
||||
<polygon points="{{ $pL - 4 }},{{ $pT }} {{ $pL }},{{ $pT - 8 }} {{ $pL + 4 }},{{ $pT }}" fill="#94a3b8"/>
|
||||
<path d="{{ $areaPath }}" fill="url(#fillGrad)"/>
|
||||
<path d="{{ $areaPath }}" fill="url(#fg)"/>
|
||||
<polyline points="{{ $poly }}" fill="none" stroke="#2563eb" stroke-width="2.5" stroke-linejoin="round" stroke-linecap="round"/>
|
||||
@foreach($pts as $p)
|
||||
<circle cx="{{ $p['x'] }}" cy="{{ $p['y'] }}" r="5" fill="white" stroke="#2563eb" stroke-width="2.5"/>
|
||||
<text x="{{ $p['x'] }}" y="{{ $p['y'] - 10 }}" text-anchor="middle" font-size="11" font-weight="700" fill="#2563eb">{{ $p['val'] }}</text>
|
||||
<text x="{{ $p['x'] }}" y="{{ $botY + 16 }}" text-anchor="middle" font-size="10" fill="#64748b">{{ Str::limit($p['name'], 9) }}</text>
|
||||
<text x="{{ $p['x'] }}" y="{{ $p['y']-10 }}" text-anchor="middle" font-size="11" font-weight="700" fill="#2563eb">{{ $p['val'] }}</text>
|
||||
<text x="{{ $p['x'] }}" y="{{ $botY+15 }}" text-anchor="middle" font-size="10" fill="#64748b">{{ Str::limit($p['name'],8) }}</text>
|
||||
@endforeach
|
||||
<text x="{{ $W - $pR + 10 }}" y="{{ $botY + 4 }}" text-anchor="start" font-size="9" fill="#94a3b8">Dusun</text>
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{-- Data per dusun --}}
|
||||
<div class="db-card">
|
||||
<div style="padding:12px 18px;border-bottom:1px solid #f1f5f9;background:#fafbfc;display:flex;align-items:center;gap:7px;">
|
||||
<div style="width:3px;height:16px;background:linear-gradient(180deg,#10b981,#059669);border-radius:4px;"></div>
|
||||
<h3 style="font-size:13px;font-weight:700;color:#1e293b;">Data Per Dusun</h3>
|
||||
<div style="padding:12px 16px;border-bottom:1px solid #f1f5f9;background:#fafbfc;display:flex;align-items:center;gap:7px;">
|
||||
<div style="width:3px;height:15px;background:linear-gradient(180deg,#10b981,#059669);border-radius:4px;"></div>
|
||||
<h3 style="font-size:13px;font-weight:700;color:#1e293b;">Per Dusun</h3>
|
||||
</div>
|
||||
<div style="padding:14px;display:flex;flex-direction:column;gap:8px;">
|
||||
@php $maxDusun = $wargaPerDusun->max('total') ?: 1; @endphp
|
||||
<div style="padding:12px;display:flex;flex-direction:column;gap:8px;">
|
||||
@php $maxDusun=$wargaPerDusun->max('total')?:1; @endphp
|
||||
@foreach($wargaPerDusun as $data)
|
||||
<div style="padding:10px 12px;background:#f8fafc;border:1.5px solid #f1f5f9;border-radius:13px;transition:border-color .15s;">
|
||||
<div style="display:flex;align-items:center;justify-content:space-between;margin-bottom:6px;">
|
||||
<div style="display:flex;align-items:center;gap:8px;">
|
||||
<div style="width:26px;height:26px;background:#eff6ff;border-radius:8px;display:flex;align-items:center;justify-content:center;flex-shrink:0;">
|
||||
<svg width="13" height="13" fill="#2563eb" 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>
|
||||
</div>
|
||||
<span style="font-size:12.5px;font-weight:700;color:#1e293b;">{{ $data->dusun }}</span>
|
||||
</div>
|
||||
<span style="font-size:14px;font-weight:900;color:#2563eb;">{{ $data->total }}</span>
|
||||
</div>
|
||||
<div style="width:100%;height:5px;background:#e2e8f0;border-radius:99px;overflow:hidden;">
|
||||
<div style="height:100%;background:linear-gradient(90deg,#3b82f6,#2563eb);border-radius:99px;width:{{ ($data->total / $maxDusun) * 100 }}%;"></div>
|
||||
</div>
|
||||
<div style="padding:10px 12px;background:#f8fafc;border:1.5px solid #f1f5f9;border-radius:12px;">
|
||||
<div style="display:flex;align-items:center;justify-content:space-between;margin-bottom:6px;">
|
||||
<span style="font-size:12px;font-weight:700;color:#1e293b;">{{ $data->dusun }}</span>
|
||||
<span style="font-size:14px;font-weight:900;color:#2563eb;">{{ $data->total }}</span>
|
||||
</div>
|
||||
<div style="width:100%;height:5px;background:#e2e8f0;border-radius:99px;overflow:hidden;">
|
||||
<div style="height:100%;background:linear-gradient(90deg,#3b82f6,#2563eb);border-radius:99px;width:{{ ($data->total/$maxDusun)*100 }}%;"></div>
|
||||
</div>
|
||||
</div>
|
||||
@endforeach
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
{{-- TOP 10 — full width, tambah kolom Dusun & RT, logika asli 100% tidak diubah --}}
|
||||
{{-- TOP 10 --}}
|
||||
<div class="db-card tt">
|
||||
<div style="padding:14px 18px;border-bottom:1.5px solid #f1f5f9;background:#fafbfc;display:flex;align-items:center;justify-content:space-between;flex-wrap:wrap;gap:10px;">
|
||||
<div style="padding:12px 16px;border-bottom:1.5px solid #f1f5f9;background:#fafbfc;display:flex;align-items:center;justify-content:space-between;flex-wrap:wrap;gap:8px;">
|
||||
<div style="display:flex;align-items:center;gap:8px;">
|
||||
<div style="width:3px;height:18px;background:linear-gradient(180deg,#3b82f6,#2563eb);border-radius:4px;"></div>
|
||||
<div style="width:3px;height:16px;background:linear-gradient(180deg,#3b82f6,#2563eb);border-radius:4px;"></div>
|
||||
<div>
|
||||
<h3 style="font-size:13px;font-weight:800;color:#0f172a;">Top 10 Probabilitas Tertinggi</h3>
|
||||
<p style="font-size:10.5px;color:#94a3b8;margin-top:1px;font-weight:500;">Seluruh dusun — diurutkan dari probabilitas terbesar</p>
|
||||
<p style="font-size:10px;color:#94a3b8;margin-top:1px;">Diurutkan dari probabilitas terbesar</p>
|
||||
</div>
|
||||
</div>
|
||||
<div style="display:flex;align-items:center;gap:12px;">
|
||||
<div style="display:flex;align-items:center;gap:10px;font-size:10.5px;color:#94a3b8;font-weight:600;">
|
||||
<span style="display:flex;align-items:center;gap:4px;"><span style="width:8px;height:8px;border-radius:50%;background:#10b981;display:inline-block;"></span>≥70%</span>
|
||||
<span style="display:flex;align-items:center;gap:4px;"><span style="width:8px;height:8px;border-radius:50%;background:#f59e0b;display:inline-block;"></span>40–70%</span>
|
||||
<span style="display:flex;align-items:center;gap:4px;"><span style="width:8px;height:8px;border-radius:50%;background:#f43f5e;display:inline-block;"></span><40%</span>
|
||||
</div>
|
||||
<a href="{{ route('admin.filterisasi') }}"
|
||||
style="display:inline-flex;align-items:center;gap:5px;font-size:11.5px;font-weight:700;color:#2563eb;background:#eff6ff;border:1px solid #bfdbfe;padding:5px 12px;border-radius:9px;text-decoration:none;">
|
||||
Lihat Semua
|
||||
<svg width="11" height="11" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5l7 7-7 7"/></svg>
|
||||
</a>
|
||||
</div>
|
||||
<a href="{{ route('admin.filterisasi') }}"
|
||||
style="display:inline-flex;align-items:center;gap:5px;font-size:11.5px;font-weight:700;color:#2563eb;background:#eff6ff;border:1px solid #bfdbfe;padding:5px 12px;border-radius:9px;text-decoration:none;">
|
||||
Lihat Semua
|
||||
<svg width="11" height="11" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5l7 7-7 7"/></svg>
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div style="overflow-x:auto;">
|
||||
<div style="overflow-x:auto;-webkit-overflow-scrolling:touch;">
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th style="width:48px;">Rank</th>
|
||||
<th>#</th>
|
||||
<th>Nama</th>
|
||||
<th>NIK</th>
|
||||
<th>Dusun</th>
|
||||
<th>RT</th>
|
||||
<th>Dusun / RT</th>
|
||||
<th>Penghasilan</th>
|
||||
<th style="min-width:160px;">Probabilitas</th>
|
||||
<th style="min-width:140px;">Probabilitas</th>
|
||||
<th>Status</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@forelse($topWarga as $index => $warga)
|
||||
<tr>
|
||||
{{-- Rank (asli) --}}
|
||||
<td>
|
||||
@if($index == 0)
|
||||
<div class="rk rk-1">1</div>
|
||||
@elseif($index == 1)
|
||||
<div class="rk rk-2">2</div>
|
||||
@elseif($index == 2)
|
||||
<div class="rk rk-3">3</div>
|
||||
@else
|
||||
<div class="rk rk-n">{{ $index + 1 }}</div>
|
||||
@endif
|
||||
</td>
|
||||
|
||||
{{-- Nama (asli) --}}
|
||||
<td>
|
||||
<div style="display:flex;align-items:center;gap:9px;">
|
||||
<div style="width:30px;height:30px;border-radius:9px;background:linear-gradient(135deg,#3b82f6,#2563eb);display:flex;align-items:center;justify-content:center;flex-shrink:0;font-size:11px;font-weight:800;color:#fff;">
|
||||
{{ strtoupper(substr($warga->nama, 0, 1)) }}
|
||||
</div>
|
||||
<div>
|
||||
<div style="font-size:12.5px;font-weight:700;color:#0f172a;">{{ $warga->nama }}</div>
|
||||
<div style="font-size:10.5px;color:#94a3b8;margin-top:1px;">{{ $warga->pekerjaan }}</div>
|
||||
</div>
|
||||
<tr>
|
||||
<td>
|
||||
@php
|
||||
$rk = ['linear-gradient(135deg,#f59e0b,#d97706)','linear-gradient(135deg,#94a3b8,#64748b)','linear-gradient(135deg,#f97316,#ea580c)'];
|
||||
$bg = $index < 3 ? $rk[$index] : '#f1f5f9';
|
||||
$tc = $index < 3 ? '#fff' : '#64748b';
|
||||
@endphp
|
||||
<div style="width:24px;height:24px;border-radius:7px;background:{{ $bg }};display:flex;align-items:center;justify-content:center;font-size:11px;font-weight:800;color:{{ $tc }};">{{ $index+1 }}</div>
|
||||
</td>
|
||||
<td>
|
||||
<div style="display:flex;align-items:center;gap:8px;">
|
||||
<div style="width:28px;height:28px;border-radius:8px;background:linear-gradient(135deg,#3b82f6,#2563eb);display:flex;align-items:center;justify-content:center;flex-shrink:0;font-size:11px;font-weight:800;color:#fff;">
|
||||
{{ strtoupper(substr($warga->nama,0,1)) }}
|
||||
</div>
|
||||
</td>
|
||||
|
||||
{{-- NIK (asli) --}}
|
||||
<td>
|
||||
<span style="font-family:monospace;font-size:10.5px;color:#6b7280;background:#f3f4f6;padding:2px 7px;border-radius:6px;">{{ $warga->nik }}</span>
|
||||
</td>
|
||||
|
||||
{{-- Dusun (ditambahkan — pakai $warga->dusun yang sudah ada di data asli) --}}
|
||||
<td>
|
||||
<span style="display:inline-flex;padding:3px 9px;border-radius:7px;background:#eff6ff;color:#1d4ed8;font-size:10.5px;font-weight:700;border:1px solid #bfdbfe;white-space:nowrap;">
|
||||
{{ $warga->dusun }}
|
||||
</span>
|
||||
</td>
|
||||
|
||||
{{-- RT (ditambahkan — pakai $warga->rt jika ada) --}}
|
||||
<td style="font-size:11px;color:#64748b;font-weight:600;">
|
||||
RT {{ str_pad($warga->rt ?? '0', 3, '0', STR_PAD_LEFT) }}
|
||||
</td>
|
||||
|
||||
{{-- Penghasilan (asli) --}}
|
||||
<td style="font-size:12px;font-weight:600;color:#374151;white-space:nowrap;">
|
||||
Rp {{ number_format($warga->penghasilan, 0, ',', '.') }}
|
||||
</td>
|
||||
|
||||
{{-- Probabilitas (asli) --}}
|
||||
<td>
|
||||
@php
|
||||
$sc = $warga->probabilitas >= 70 ? '#10b981' : ($warga->probabilitas >= 40 ? '#f59e0b' : '#f43f5e');
|
||||
$scB = $warga->probabilitas >= 70 ? '#f0fdf4' : ($warga->probabilitas >= 40 ? '#fffbeb' : '#fff1f2');
|
||||
@endphp
|
||||
<div style="display:flex;align-items:center;gap:8px;">
|
||||
<div style="flex:1;background:#f1f5f9;border-radius:99px;height:6px;overflow:hidden;min-width:80px;">
|
||||
<div style="height:100%;border-radius:99px;background:{{ $sc }};width:{{ min($warga->probabilitas, 100) }}%;"></div>
|
||||
</div>
|
||||
<span style="font-size:12px;font-weight:800;color:{{ $sc }};white-space:nowrap;">
|
||||
{{ number_format($warga->probabilitas, 1) }}%
|
||||
</span>
|
||||
<div>
|
||||
<div style="font-size:12px;font-weight:700;color:#0f172a;white-space:nowrap;">{{ $warga->nama }}</div>
|
||||
<div style="font-size:10px;color:#94a3b8;">{{ $warga->pekerjaan }}</div>
|
||||
</div>
|
||||
</td>
|
||||
|
||||
{{-- Status (asli) --}}
|
||||
<td>
|
||||
@if($warga->status == 'pending')
|
||||
<span style="display:inline-flex;align-items:center;gap:4px;padding:4px 10px;font-size:10.5px;font-weight:700;border-radius:20px;background:#fffbeb;color:#b45309;border:1px solid #fde68a;white-space:nowrap;">
|
||||
<span style="width:5px;height:5px;border-radius:50%;background:#f59e0b;flex-shrink:0;"></span>Pending
|
||||
</span>
|
||||
@elseif($warga->status == 'diterima')
|
||||
<span style="display:inline-flex;align-items:center;gap:4px;padding:4px 10px;font-size:10.5px;font-weight:700;border-radius:20px;background:#f0fdf4;color:#166534;border:1px solid #bbf7d0;white-space:nowrap;">
|
||||
<span style="width:5px;height:5px;border-radius:50%;background:#22c55e;flex-shrink:0;"></span>Diterima
|
||||
</span>
|
||||
@elseif($warga->status == 'ditolak')
|
||||
<span style="display:inline-flex;align-items:center;gap:4px;padding:4px 10px;font-size:10.5px;font-weight:700;border-radius:20px;background:#fff1f2;color:#be123c;border:1px solid #fecdd3;white-space:nowrap;">
|
||||
<span style="width:5px;height:5px;border-radius:50%;background:#f43f5e;flex-shrink:0;"></span>Ditolak
|
||||
</span>
|
||||
@else
|
||||
<span style="display:inline-flex;align-items:center;gap:4px;padding:4px 10px;font-size:10.5px;font-weight:700;border-radius:20px;background:#eff6ff;color:#1d4ed8;border:1px solid #bfdbfe;white-space:nowrap;">
|
||||
<span style="width:5px;height:5px;border-radius:50%;background:#3b82f6;flex-shrink:0;"></span>{{ ucfirst($warga->status) }}
|
||||
</span>
|
||||
@endif
|
||||
</td>
|
||||
</tr>
|
||||
</div>
|
||||
</td>
|
||||
<td><span style="font-family:monospace;font-size:10.5px;color:#6b7280;background:#f3f4f6;padding:2px 7px;border-radius:6px;white-space:nowrap;">{{ $warga->nik }}</span></td>
|
||||
<td>
|
||||
<div style="white-space:nowrap;">
|
||||
<span style="display:inline-block;padding:2px 8px;border-radius:6px;background:#eff6ff;color:#1d4ed8;font-size:10.5px;font-weight:700;border:1px solid #bfdbfe;">{{ $warga->dusun }}</span>
|
||||
<span style="font-size:10.5px;color:#64748b;font-weight:600;margin-left:4px;">RT {{ str_pad($warga->rt??'0',3,'0',STR_PAD_LEFT) }}</span>
|
||||
</div>
|
||||
</td>
|
||||
<td style="white-space:nowrap;font-weight:600;">Rp {{ number_format($warga->penghasilan,0,',','.') }}</td>
|
||||
<td>
|
||||
@php $sc=$warga->probabilitas>=70?'#10b981':($warga->probabilitas>=40?'#f59e0b':'#f43f5e'); @endphp
|
||||
<div style="display:flex;align-items:center;gap:6px;">
|
||||
<div style="flex:1;background:#f1f5f9;border-radius:99px;height:5px;overflow:hidden;min-width:70px;">
|
||||
<div style="height:100%;border-radius:99px;background:{{ $sc }};width:{{ min($warga->probabilitas,100) }}%;"></div>
|
||||
</div>
|
||||
<span style="font-size:11.5px;font-weight:800;color:{{ $sc }};white-space:nowrap;">{{ number_format($warga->probabilitas,1) }}%</span>
|
||||
</div>
|
||||
</td>
|
||||
<td>
|
||||
@php
|
||||
$st=['pending'=>['#fffbeb','#b45309','#fde68a','Pending'],'diterima'=>['#f0fdf4','#166534','#bbf7d0','Diterima'],'ditolak'=>['#fff1f2','#be123c','#fecdd3','Ditolak']];
|
||||
$s=$st[$warga->status]??['#eff6ff','#1d4ed8','#bfdbfe',ucfirst($warga->status)];
|
||||
@endphp
|
||||
<span style="display:inline-flex;align-items:center;gap:4px;padding:3px 9px;font-size:10.5px;font-weight:700;border-radius:20px;background:{{ $s[0] }};color:{{ $s[1] }};border:1px solid {{ $s[2] }};white-space:nowrap;">
|
||||
<span style="width:5px;height:5px;border-radius:50%;background:{{ $s[1] }};flex-shrink:0;"></span>{{ $s[3] }}
|
||||
</span>
|
||||
</td>
|
||||
</tr>
|
||||
@empty
|
||||
<tr>
|
||||
<td colspan="8">
|
||||
<div style="padding:48px 16px;text-align:center;">
|
||||
<div style="width:44px;height:44px;background:#f3f4f6;border-radius:50%;display:flex;align-items:center;justify-content:center;margin:0 auto 10px;">
|
||||
<svg width="20" height="20" fill="none" stroke="#d1d5db" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" d="M9 19v-6a2 2 0 00-2-2H5a2 2 0 00-2 2v6a2 2 0 002 2h2a2 2 0 002-2zm0 0V9a2 2 0 012-2h2a2 2 0 012 2v10m-6 0a2 2 0 002 2h2a2 2 0 002-2m0 0V5a2 2 0 012-2h2a2 2 0 012 2v14a2 2 0 01-2 2h-2a2 2 0 01-2-2z"/></svg>
|
||||
</div>
|
||||
<p style="font-size:13px;font-weight:600;color:#94a3b8;">Belum ada data warga</p>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
<tr><td colspan="7" style="text-align:center;padding:40px;color:#94a3b8;font-size:13px;">Belum ada data warga</td></tr>
|
||||
@endforelse
|
||||
</tbody>
|
||||
</table>
|
||||
|
|
|
|||
|
|
@ -1,299 +1,383 @@
|
|||
<x-app-layout>
|
||||
|
||||
<x-slot name="header">
|
||||
<div class="flex items-center justify-between">
|
||||
<div style="display:flex;align-items:center;justify-content:space-between;gap:12px;flex-wrap:wrap;">
|
||||
<div>
|
||||
<h2 class="font-semibold text-xl text-gray-800 leading-tight">Data Akun</h2>
|
||||
<p class="text-sm text-gray-500 mt-0.5">Kelola akun Admin & RT yang menggunakan sistem.</p>
|
||||
<h2 style="font-size:16px;font-weight:800;color:#0f172a;letter-spacing:-.02em;">Data Akun</h2>
|
||||
<p style="font-size:11px;color:#94a3b8;margin-top:2px;">Kelola akun Admin & RT yang menggunakan sistem</p>
|
||||
</div>
|
||||
<div class="inline-flex items-center gap-2 bg-gray-50 border border-gray-200 rounded-xl px-3 py-1.5 text-xs text-gray-500">
|
||||
<svg class="w-3.5 h-3.5 text-blue-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 4.354a4 4 0 110 5.292M15 21H3v-1a6 6 0 0112 0v1zm0 0h6v-1a6 6 0 00-9-5.197M13 7a4 4 0 11-8 0 4 4 0 018 0z"/>
|
||||
</svg>
|
||||
@if(isset($users) && method_exists($users, 'total'))
|
||||
{{ $users->total() }} akun terdaftar
|
||||
@endif
|
||||
@if(isset($users) && method_exists($users,'total'))
|
||||
<div style="display:inline-flex;align-items:center;gap:6px;background:#f8fafc;border:1.5px solid #e2e8f0;border-radius:10px;padding:6px 12px;font-size:11px;color:#64748b;">
|
||||
<svg width="13" height="13" fill="none" stroke="#3b82f6" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 4.354a4 4 0 110 5.292M15 21H3v-1a6 6 0 0112 0v1zm0 0h6v-1a6 6 0 00-9-5.197M13 7a4 4 0 11-8 0 4 4 0 018 0z"/></svg>
|
||||
{{ $users->total() }} akun terdaftar
|
||||
</div>
|
||||
@endif
|
||||
</div>
|
||||
</x-slot>
|
||||
|
||||
<div class="space-y-4">
|
||||
<style>
|
||||
.da-card{background:#fff;border-radius:16px;border:1.5px solid #f1f5f9;box-shadow:0 1px 4px rgba(0,0,0,.04);overflow:hidden;}
|
||||
.fl-box{display:flex;align-items:center;gap:9px;border-radius:12px;padding:10px 14px;font-size:12px;font-weight:500;margin-bottom:10px;}
|
||||
.fl-box svg{width:14px;height:14px;flex-shrink:0;}
|
||||
|
||||
{{-- NOTIFIKASI --}}
|
||||
@if(session('success'))
|
||||
<div class="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>
|
||||
{{ session('success') }}
|
||||
</div>
|
||||
@endif
|
||||
@if(session('error'))
|
||||
<div class="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>
|
||||
{{ session('error') }}
|
||||
</div>
|
||||
@endif
|
||||
/* search */
|
||||
.sw{position:relative;}
|
||||
.sw svg{position:absolute;left:11px;top:50%;transform:translateY(-50%);width:14px;height:14px;stroke:#9ca3af;pointer-events:none;}
|
||||
.sw input{width:100%;padding:9px 12px 9px 34px;font-size:13px;color:#111827;background:#f9fafb;border:1.5px solid #e5e7eb;border-radius:11px;outline:none;transition:border-color .15s,box-shadow .15s;font-family:inherit;}
|
||||
.sw input:focus{border-color:#3b82f6;background:#fff;box-shadow:0 0 0 3px rgba(59,130,246,.10);}
|
||||
.sw input::placeholder{color:#9ca3af;}
|
||||
|
||||
{{-- SEARCH --}}
|
||||
<div class="bg-white rounded-2xl shadow-sm border border-gray-100 p-5">
|
||||
<div class="flex items-center gap-2 mb-3">
|
||||
<div class="w-1 h-4 bg-blue-600 rounded-full"></div>
|
||||
<h3 class="text-sm font-semibold text-gray-700">Pencarian</h3>
|
||||
</div>
|
||||
<form method="GET" action="{{ route('admin.data-akun') }}" class="flex flex-col md:flex-row gap-3">
|
||||
<div class="relative flex-1">
|
||||
<svg class="absolute left-3 top-1/2 -translate-y-1/2 w-4 h-4 text-gray-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z"/>
|
||||
</svg>
|
||||
<input type="text" name="q" value="{{ request('q') }}"
|
||||
placeholder="Cari nama / email..."
|
||||
class="w-full pl-9 pr-3 py-2 text-sm border border-gray-200 rounded-xl bg-gray-50 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent"/>
|
||||
</div>
|
||||
<div class="flex gap-2">
|
||||
<button type="submit"
|
||||
class="inline-flex items-center justify-center gap-2 px-5 py-2 text-sm font-semibold bg-blue-600 text-white rounded-xl hover:bg-blue-700 transition shadow-sm 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="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z"/>
|
||||
</svg>
|
||||
Cari
|
||||
</button>
|
||||
@if(request()->filled('q'))
|
||||
<a href="{{ route('admin.data-akun') }}"
|
||||
class="inline-flex items-center justify-center w-10 text-sm bg-gray-100 text-gray-500 rounded-xl hover:bg-gray-200 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="M6 18L18 6M6 6l12 12"/>
|
||||
</svg>
|
||||
</a>
|
||||
@endif
|
||||
</div>
|
||||
</form>
|
||||
/* table */
|
||||
.tbl-wrap{overflow-x:auto;-webkit-overflow-scrolling:touch;}
|
||||
table{width:100%;border-collapse:collapse;min-width:580px;}
|
||||
thead tr{background:#f8fafc;border-bottom:1.5px solid #f1f5f9;}
|
||||
thead th{padding:9px 14px;text-align:left;font-size:9.5px;font-weight:700;color:#94a3b8;text-transform:uppercase;letter-spacing:.07em;white-space:nowrap;}
|
||||
tbody tr{border-bottom:1px solid #f8fafc;transition:background .1s;}
|
||||
tbody tr:hover{background:#f0f7ff;}
|
||||
tbody tr:last-child{border-bottom:none;}
|
||||
tbody td{padding:10px 14px;vertical-align:middle;}
|
||||
|
||||
/* 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;}
|
||||
|
||||
/* action buttons */
|
||||
.btn-a{display:inline-flex;align-items:center;gap:4px;padding:4px 10px;border-radius:8px;font-size:10.5px;font-weight:600;border:none;cursor:pointer;white-space:nowrap;font-family:inherit;text-decoration:none;line-height:1.4;transition:filter .1s;}
|
||||
.btn-a:hover{filter:brightness(.92);}
|
||||
.btn-a svg{width:11px;height:11px;flex-shrink:0;}
|
||||
.btn-role{background:#f1f5f9;color:#475569;}
|
||||
.btn-nonaktif{background:#fffbeb;color:#b45309;}
|
||||
.btn-aktif{background:#f0fdf4;color:#166534;}
|
||||
.btn-hapus{background:#fff1f2;color:#be123c;}
|
||||
.btn-locked{background:#f8fafc;color:#94a3b8;border:1.5px solid #e2e8f0;cursor:default;}
|
||||
|
||||
/* avatar */
|
||||
.av-wrap{position:relative;flex-shrink:0;}
|
||||
.av{width:34px;height:34px;border-radius:50%;display:flex;align-items:center;justify-content:center;font-size:13px;font-weight:800;color:#fff;}
|
||||
.av-dot{position:absolute;bottom:-1px;right:-1px;width:10px;height:10px;border-radius:50%;background:#10b981;border:2px solid #fff;}
|
||||
|
||||
/* mobile card view */
|
||||
.mob-card{display:none;flex-direction:column;gap:10px;padding:14px 16px;}
|
||||
.mob-row{background:#f8fafc;border-radius:13px;padding:12px 14px;border:1.5px solid #f1f5f9;}
|
||||
.mob-name{font-size:13px;font-weight:700;color:#0f172a;}
|
||||
.mob-email{font-size:11px;color:#64748b;margin-top:2px;}
|
||||
.mob-meta{display:flex;align-items:center;gap:6px;flex-wrap:wrap;margin-top:8px;}
|
||||
.mob-actions{display:flex;align-items:center;gap:6px;flex-wrap:wrap;margin-top:10px;padding-top:10px;border-top:1px solid #f1f5f9;}
|
||||
|
||||
@media(max-width:640px){
|
||||
.tbl-wrap table{display:none;}
|
||||
.mob-card{display:flex;}
|
||||
}
|
||||
</style>
|
||||
|
||||
{{-- FLASH --}}
|
||||
@if(session('success'))
|
||||
<div class="fl-box" 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="fl-box" 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
|
||||
|
||||
{{-- TABLE CARD --}}
|
||||
<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 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">
|
||||
Daftar Akun
|
||||
@if(isset($users) && method_exists($users, 'total'))
|
||||
<span class="ml-1 text-gray-400 font-normal">({{ $users->total() }} akun)</span>
|
||||
@endif
|
||||
</span>
|
||||
</div>
|
||||
@if(isset($users) && method_exists($users, 'currentPage') && $users->lastPage() > 1)
|
||||
<span class="text-xs text-gray-400">Hal. {{ $users->currentPage() }} / {{ $users->lastPage() }}</span>
|
||||
@endif
|
||||
{{-- SEARCH --}}
|
||||
<div class="da-card" style="padding:14px 16px;margin-bottom:12px;">
|
||||
<div style="display:flex;align-items:center;gap:7px;margin-bottom:10px;">
|
||||
<div style="width:3px;height:14px;background:#2563eb;border-radius:4px;"></div>
|
||||
<h3 style="font-size:12px;font-weight:700;color:#374151;">Pencarian</h3>
|
||||
</div>
|
||||
<form method="GET" action="{{ route('admin.data-akun') }}"
|
||||
style="display:flex;gap:8px;align-items:center;flex-wrap:wrap;">
|
||||
<div class="sw" style="flex:1;min-width:200px;">
|
||||
<svg fill="none" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z"/></svg>
|
||||
<input type="text" name="q" value="{{ request('q') }}" placeholder="Cari nama atau email...">
|
||||
</div>
|
||||
|
||||
<div class="overflow-x-auto w-full">
|
||||
|
||||
<table class="w-full text-sm table-fixed">
|
||||
<colgroup>
|
||||
<col style="width:28%">
|
||||
<col style="width:23%">
|
||||
<col style="width:11%">
|
||||
<col style="width:11%">
|
||||
<col style="width:27%">
|
||||
</colgroup>
|
||||
<thead>
|
||||
<tr class="bg-gray-50/80 border-b border-gray-100">
|
||||
<th class="px-4 py-3 text-left text-xs font-semibold text-gray-400 uppercase tracking-wide">Nama</th>
|
||||
<th class="px-4 py-3 text-left text-xs font-semibold text-gray-400 uppercase tracking-wide">Email</th>
|
||||
<th class="px-4 py-3 text-left text-xs font-semibold text-gray-400 uppercase tracking-wide">Role</th>
|
||||
<th class="px-4 py-3 text-left text-xs font-semibold text-gray-400 uppercase tracking-wide">Status</th>
|
||||
<th class="px-4 py-3 text-right text-xs font-semibold text-gray-400 uppercase tracking-wide">Aksi</th>
|
||||
</tr>
|
||||
</thead>
|
||||
|
||||
<tbody class="divide-y divide-gray-100">
|
||||
@forelse($users ?? [] as $u)
|
||||
@php
|
||||
$isMe = ($u->id === auth()->id());
|
||||
$isActive = isset($u->is_active) ? (bool)$u->is_active : true;
|
||||
$isAdmin = $u->role === 'admin';
|
||||
$newRole = $isAdmin ? 'rt' : 'admin';
|
||||
$newRoleLabel = $isAdmin ? 'RT' : 'Admin';
|
||||
$initial = strtoupper(substr($u->name, 0, 1));
|
||||
$avatarGrad = $isAdmin
|
||||
? 'from-emerald-500 to-emerald-600 shadow-emerald-200'
|
||||
: 'from-blue-500 to-blue-600 shadow-blue-200';
|
||||
@endphp
|
||||
|
||||
<tr class="hover:bg-blue-50/20 transition-colors duration-150 {{ !$isActive ? 'opacity-50' : '' }}">
|
||||
|
||||
{{-- NAMA --}}
|
||||
<td class="px-4 py-6">
|
||||
<div class="flex items-center gap-2.5">
|
||||
<div class="relative shrink-0">
|
||||
<div class="w-10 h-10 rounded-full bg-gradient-to-br {{ $avatarGrad }} flex items-center justify-center shadow-sm">
|
||||
<span class="text-sm font-bold text-white">{{ $initial }}</span>
|
||||
</div>
|
||||
@if($isActive)
|
||||
<span class="absolute -bottom-0.5 -right-0.5 w-3 h-3 bg-emerald-500 border-2 border-white rounded-full"></span>
|
||||
@endif
|
||||
</div>
|
||||
<div class="min-w-0">
|
||||
<div class="text-sm font-semibold text-gray-800 truncate flex items-center gap-1.5 flex-wrap">
|
||||
{{ $u->name }}
|
||||
@if($isMe)
|
||||
<span class="text-[10px] px-1.5 py-0.5 rounded-full bg-indigo-100 text-indigo-600 border border-indigo-200 font-semibold leading-none">Kamu</span>
|
||||
@endif
|
||||
</div>
|
||||
<div class="text-xs text-gray-400 mt-0.5">ID #{{ $u->id }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</td>
|
||||
|
||||
{{-- EMAIL --}}
|
||||
<td class="px-4 py-6">
|
||||
<span class="text-sm text-gray-500 truncate block">{{ $u->email }}</span>
|
||||
</td>
|
||||
|
||||
{{-- ROLE --}}
|
||||
<td class="px-4 py-6">
|
||||
@if($isAdmin)
|
||||
<span class="inline-flex items-center gap-1 px-2 py-1 text-xs font-semibold rounded-lg bg-emerald-100 text-emerald-700 border border-emerald-200">
|
||||
<svg class="w-3 h-3" fill="currentColor" viewBox="0 0 20 20">
|
||||
<path fill-rule="evenodd" d="M18 10a8 8 0 11-16 0 8 8 0 0116 0zm-6-3a2 2 0 11-4 0 2 2 0 014 0zm-2 4a5 5 0 00-4.546 2.916A5.986 5.986 0 0010 16a5.986 5.986 0 004.546-2.084A5 5 0 0010 11z" clip-rule="evenodd"/>
|
||||
</svg>
|
||||
Admin
|
||||
</span>
|
||||
@else
|
||||
<span class="inline-flex items-center gap-1 px-2 py-1 text-xs font-semibold rounded-lg bg-blue-100 text-blue-700 border border-blue-200">
|
||||
<svg class="w-3 h-3" fill="currentColor" viewBox="0 0 20 20">
|
||||
<path d="M9 6a3 3 0 11-6 0 3 3 0 016 0zM17 6a3 3 0 11-6 0 3 3 0 016 0zM12.93 17c.046-.327.07-.66.07-1a6.97 6.97 0 00-1.5-4.33A5 5 0 0119 16v1h-6.07zM6 11a5 5 0 015 5v1H1v-1a5 5 0 015-5z"/>
|
||||
</svg>
|
||||
RT
|
||||
</span>
|
||||
@endif
|
||||
</td>
|
||||
|
||||
{{-- STATUS --}}
|
||||
<td class="px-4 py-6">
|
||||
@if($isActive)
|
||||
<span class="inline-flex items-center gap-1 px-2 py-1 text-xs font-semibold rounded-lg bg-emerald-50 text-emerald-700 border border-emerald-100">
|
||||
<span class="w-1.5 h-1.5 rounded-full bg-emerald-500 animate-pulse"></span>
|
||||
Aktif
|
||||
</span>
|
||||
@else
|
||||
<span class="inline-flex items-center gap-1 px-2 py-1 text-xs font-semibold rounded-lg bg-gray-100 text-gray-400 border border-gray-200">
|
||||
<span class="w-1.5 h-1.5 rounded-full bg-gray-400"></span>
|
||||
Nonaktif
|
||||
</span>
|
||||
@endif
|
||||
</td>
|
||||
|
||||
{{-- AKSI --}}
|
||||
<td class="px-4 py-6">
|
||||
<div class="flex items-center justify-end gap-1.5">
|
||||
@if($isMe)
|
||||
<span class="inline-flex items-center gap-1.5 px-2.5 py-1 text-xs font-medium bg-gray-100 text-gray-400 rounded-lg border border-gray-200">
|
||||
<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="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>
|
||||
Terkunci
|
||||
</span>
|
||||
@else
|
||||
{{-- UBAH ROLE --}}
|
||||
<form action="{{ route('admin.data-akun.role', $u->id) }}" method="POST">
|
||||
@csrf
|
||||
<input type="hidden" name="role" value="{{ $newRole }}">
|
||||
<button type="submit"
|
||||
onclick="return confirm('Ubah role {{ addslashes($u->name) }} menjadi {{ $newRoleLabel }}?')"
|
||||
class="inline-flex items-center gap-1 px-2.5 py-1 text-xs font-medium bg-slate-100 text-slate-600 rounded-lg hover:bg-slate-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="M8 7h12m0 0l-4-4m4 4l-4 4m0 6H4m0 0l4 4m-4-4l4-4"/>
|
||||
</svg>
|
||||
→ {{ $newRoleLabel }}
|
||||
</button>
|
||||
</form>
|
||||
|
||||
{{-- AKTIF / NONAKTIF --}}
|
||||
<form action="{{ route('admin.data-akun.toggle-aktif', $u->id) }}" method="POST">
|
||||
@csrf
|
||||
<button type="submit"
|
||||
onclick="return confirm('{{ $isActive ? 'Nonaktifkan' : 'Aktifkan' }} akun {{ addslashes($u->name) }}?')"
|
||||
class="inline-flex items-center gap-1 px-2.5 py-1 text-xs font-medium rounded-lg transition
|
||||
{{ $isActive ? 'bg-amber-100 text-amber-700 hover:bg-amber-200' : 'bg-emerald-100 text-emerald-700 hover:bg-emerald-200' }}">
|
||||
@if($isActive)
|
||||
<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="M18.364 18.364A9 9 0 005.636 5.636m12.728 12.728A9 9 0 015.636 5.636m12.728 12.728L5.636 5.636"/>
|
||||
</svg>
|
||||
Nonaktifkan
|
||||
@else
|
||||
<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="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z"/>
|
||||
</svg>
|
||||
Aktifkan
|
||||
@endif
|
||||
</button>
|
||||
</form>
|
||||
|
||||
{{-- HAPUS --}}
|
||||
<form action="{{ route('admin.data-akun.hapus', $u->id) }}" method="POST"
|
||||
onsubmit="return confirm('Yakin hapus akun {{ addslashes($u->name) }}? Tindakan ini tidak bisa dibatalkan.')">
|
||||
@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>
|
||||
Hapus
|
||||
</button>
|
||||
</form>
|
||||
@endif
|
||||
</div>
|
||||
</td>
|
||||
|
||||
</tr>
|
||||
@empty
|
||||
<tr>
|
||||
<td colspan="5" class="py-16 text-center">
|
||||
<div class="flex flex-col items-center gap-3">
|
||||
<div class="w-14 h-14 bg-gray-100 rounded-2xl flex items-center justify-center">
|
||||
<svg class="w-7 h-7 text-gray-300" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" d="M12 4.354a4 4 0 110 5.292M15 21H3v-1a6 6 0 0112 0v1zm0 0h6v-1a6 6 0 00-9-5.197M13 7a4 4 0 11-8 0 4 4 0 018 0z"/>
|
||||
</svg>
|
||||
</div>
|
||||
<div>
|
||||
<p class="text-sm font-semibold text-gray-500">Belum ada akun</p>
|
||||
@if(request()->filled('q'))
|
||||
<p class="text-xs text-gray-400 mt-0.5">Tidak ada hasil untuk "<span class="font-medium">{{ request('q') }}</span>"</p>
|
||||
<a href="{{ route('admin.data-akun') }}" class="inline-block mt-2 text-xs text-blue-600 hover:underline">← Hapus pencarian</a>
|
||||
@endif
|
||||
</div>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
@endforelse
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
{{-- PAGINATION --}}
|
||||
@if(isset($users) && method_exists($users, 'links') && $users->hasPages())
|
||||
<div class="px-5 py-3 border-t border-gray-100 bg-gray-50">
|
||||
{{ $users->appends(request()->query())->links() }}
|
||||
</div>
|
||||
<button type="submit"
|
||||
style="display:inline-flex;align-items:center;gap:6px;padding:9px 16px;background:#2563eb;color:#fff;font-size:12px;font-weight:700;border:none;border-radius:11px;cursor:pointer;white-space:nowrap;font-family:inherit;">
|
||||
<svg width="13" height="13" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z"/></svg>
|
||||
Cari
|
||||
</button>
|
||||
@if(request()->filled('q'))
|
||||
<a href="{{ route('admin.data-akun') }}"
|
||||
style="display:inline-flex;align-items:center;justify-content:center;width:36px;height:36px;background:#f3f4f6;border-radius:10px;color:#6b7280;text-decoration:none;">
|
||||
<svg width="14" height="14" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12"/></svg>
|
||||
</a>
|
||||
@endif
|
||||
|
||||
{{-- CREDIT WATERMARK --}}
|
||||
<div class="px-5 py-5 border-t border-gray-100 flex items-center justify-center gap-2 select-none mt-1">
|
||||
<svg class="w-3.5 h-3.5 text-gray-300" fill="currentColor" 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 class="text-xs text-gray-300 font-medium tracking-wide">Aplikasi SiBantuDes</span>
|
||||
<span class="text-gray-200">·</span>
|
||||
<span class="text-xs text-gray-300 tracking-wide">Desa Ngerong</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</form>
|
||||
</div>
|
||||
|
||||
{{-- TABLE CARD --}}
|
||||
<div class="da-card">
|
||||
<div style="padding:10px 16px;border-bottom:1px solid #f1f5f9;background:#fafafa;display:flex;align-items:center;justify-content:space-between;flex-wrap:wrap;gap:6px;">
|
||||
<div style="display:flex;align-items:center;gap:7px;">
|
||||
<div style="width:3px;height:15px;background:#2563eb;border-radius:4px;"></div>
|
||||
<span style="font-size:12.5px;font-weight:700;color:#374151;">
|
||||
Daftar Akun
|
||||
@if(isset($users) && method_exists($users,'total'))
|
||||
<span style="color:#9ca3af;font-weight:400;font-size:11px;"> ({{ $users->total() }} akun)</span>
|
||||
@endif
|
||||
</span>
|
||||
</div>
|
||||
@if(isset($users) && method_exists($users,'currentPage') && $users->lastPage() > 1)
|
||||
<span style="font-size:11px;color:#9ca3af;">Hal. {{ $users->currentPage() }} / {{ $users->lastPage() }}</span>
|
||||
@endif
|
||||
</div>
|
||||
|
||||
{{-- ── DESKTOP TABLE ── --}}
|
||||
<div class="tbl-wrap">
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Nama</th>
|
||||
<th>Email</th>
|
||||
<th>Role</th>
|
||||
<th>Status</th>
|
||||
<th style="text-align:right;">Aksi</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@forelse($users ?? [] as $u)
|
||||
@php
|
||||
$isMe = ($u->id === auth()->id());
|
||||
$isActive = isset($u->is_active) ? (bool)$u->is_active : true;
|
||||
$isAdmin = $u->role === 'admin';
|
||||
$newRole = $isAdmin ? 'rt' : 'admin';
|
||||
$newRoleLabel = $isAdmin ? 'RT' : 'Admin';
|
||||
$initial = strtoupper(substr($u->name,0,1));
|
||||
$avBg = $isAdmin
|
||||
? 'background:linear-gradient(135deg,#10b981,#059669);'
|
||||
: 'background:linear-gradient(135deg,#3b82f6,#2563eb);';
|
||||
@endphp
|
||||
<tr style="{{ !$isActive ? 'opacity:.5;' : '' }}">
|
||||
|
||||
{{-- NAMA --}}
|
||||
<td>
|
||||
<div style="display:flex;align-items:center;gap:10px;">
|
||||
<div class="av-wrap">
|
||||
<div class="av" style="{{ $avBg }}">{{ $initial }}</div>
|
||||
@if($isActive)<div class="av-dot"></div>@endif
|
||||
</div>
|
||||
<div style="min-width:0;">
|
||||
<div style="display:flex;align-items:center;gap:6px;flex-wrap:wrap;">
|
||||
<span style="font-size:12.5px;font-weight:700;color:#0f172a;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;max-width:130px;">{{ $u->name }}</span>
|
||||
@if($isMe)
|
||||
<span style="font-size:9.5px;padding:1px 7px;border-radius:20px;background:#ede9fe;color:#6d28d9;border:1px solid #ddd6fe;font-weight:700;">Kamu</span>
|
||||
@endif
|
||||
</div>
|
||||
<div style="font-size:10.5px;color:#94a3b8;margin-top:1px;">ID #{{ $u->id }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</td>
|
||||
|
||||
{{-- EMAIL --}}
|
||||
<td style="font-size:12px;color:#64748b;max-width:180px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;">
|
||||
{{ $u->email }}
|
||||
</td>
|
||||
|
||||
{{-- ROLE --}}
|
||||
<td>
|
||||
@if($isAdmin)
|
||||
<span class="badge" style="background:#f0fdf4;color:#166534;border:1px solid #bbf7d0;">
|
||||
<span class="dot" style="background:#10b981;"></span>Admin
|
||||
</span>
|
||||
@else
|
||||
<span class="badge" style="background:#eff6ff;color:#1d4ed8;border:1px solid #bfdbfe;">
|
||||
<span class="dot" style="background:#3b82f6;"></span>RT
|
||||
</span>
|
||||
@endif
|
||||
</td>
|
||||
|
||||
{{-- STATUS --}}
|
||||
<td>
|
||||
@if($isActive)
|
||||
<span class="badge" style="background:#f0fdf4;color:#166534;border:1px solid #bbf7d0;">
|
||||
<span class="dot" style="background:#10b981;animation:pulse 2s infinite;"></span>Aktif
|
||||
</span>
|
||||
@else
|
||||
<span class="badge" style="background:#f3f4f6;color:#6b7280;border:1px solid #e5e7eb;">
|
||||
<span class="dot" style="background:#9ca3af;"></span>Nonaktif
|
||||
</span>
|
||||
@endif
|
||||
</td>
|
||||
|
||||
{{-- AKSI --}}
|
||||
<td>
|
||||
<div style="display:flex;align-items:center;justify-content:flex-end;gap:5px;flex-wrap:wrap;">
|
||||
@if($isMe)
|
||||
<span class="btn-a btn-locked">
|
||||
<svg fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 15v2m-6 4h12a2 2 0 002-2v-6a2 2 0 00-2-2H6a2 2 0 00-2 2v6a2 2 0 002 2zm10-10V7a4 4 0 00-8 0v4h8z"/></svg>
|
||||
Terkunci
|
||||
</span>
|
||||
@else
|
||||
{{-- Ubah role --}}
|
||||
<form method="POST" action="{{ route('admin.data-akun.role',$u->id) }}">
|
||||
@csrf
|
||||
<input type="hidden" name="role" value="{{ $newRole }}">
|
||||
<button type="submit" class="btn-a btn-role"
|
||||
onclick="return confirm('Ubah role {{ addslashes($u->name) }} menjadi {{ $newRoleLabel }}?')">
|
||||
<svg fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 7h12m0 0l-4-4m4 4l-4 4m0 6H4m0 0l4 4m-4-4l4-4"/></svg>
|
||||
→ {{ $newRoleLabel }}
|
||||
</button>
|
||||
</form>
|
||||
|
||||
{{-- Toggle aktif --}}
|
||||
<form method="POST" action="{{ route('admin.data-akun.toggle-aktif',$u->id) }}">
|
||||
@csrf
|
||||
<button type="submit"
|
||||
class="btn-a {{ $isActive ? 'btn-nonaktif' : 'btn-aktif' }}"
|
||||
onclick="return confirm('{{ $isActive ? 'Nonaktifkan' : 'Aktifkan' }} akun {{ addslashes($u->name) }}?')">
|
||||
@if($isActive)
|
||||
<svg fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M18.364 18.364A9 9 0 005.636 5.636m12.728 12.728A9 9 0 015.636 5.636m12.728 12.728L5.636 5.636"/></svg>
|
||||
Nonaktifkan
|
||||
@else
|
||||
<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>
|
||||
Aktifkan
|
||||
@endif
|
||||
</button>
|
||||
</form>
|
||||
|
||||
{{-- Hapus --}}
|
||||
<form method="POST" action="{{ route('admin.data-akun.hapus',$u->id) }}"
|
||||
onsubmit="return confirm('Yakin hapus akun {{ addslashes($u->name) }}? Tindakan ini tidak bisa dibatalkan.')">
|
||||
@csrf @method('DELETE')
|
||||
<button type="submit" class="btn-a 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="5">
|
||||
<div style="padding:48px 16px;text-align:center;">
|
||||
<div style="width:44px;height:44px;background:#f3f4f6;border-radius:50%;display:flex;align-items:center;justify-content:center;margin:0 auto 10px;">
|
||||
<svg width="22" height="22" fill="none" stroke="#d1d5db" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" d="M12 4.354a4 4 0 110 5.292M15 21H3v-1a6 6 0 0112 0v1zm0 0h6v-1a6 6 0 00-9-5.197M13 7a4 4 0 11-8 0 4 4 0 018 0z"/></svg>
|
||||
</div>
|
||||
<p style="font-size:13px;font-weight:600;color:#6b7280;">Belum ada akun</p>
|
||||
@if(request()->filled('q'))
|
||||
<p style="font-size:11.5px;color:#9ca3af;margin-top:4px;">Tidak ada hasil untuk "<strong>{{ request('q') }}</strong>"</p>
|
||||
<a href="{{ route('admin.data-akun') }}" style="display:inline-block;margin-top:8px;font-size:12px;color:#2563eb;font-weight:600;text-decoration:none;">← Hapus pencarian</a>
|
||||
@endif
|
||||
</div>
|
||||
</td></tr>
|
||||
@endforelse
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
{{-- ── MOBILE CARD VIEW ── --}}
|
||||
<div class="mob-card">
|
||||
@forelse($users ?? [] as $u)
|
||||
@php
|
||||
$isMe = ($u->id === auth()->id());
|
||||
$isActive = isset($u->is_active) ? (bool)$u->is_active : true;
|
||||
$isAdmin = $u->role === 'admin';
|
||||
$newRole = $isAdmin ? 'rt' : 'admin';
|
||||
$newRoleLabel = $isAdmin ? 'RT' : 'Admin';
|
||||
$initial = strtoupper(substr($u->name,0,1));
|
||||
$avBg = $isAdmin
|
||||
? 'background:linear-gradient(135deg,#10b981,#059669);'
|
||||
: 'background:linear-gradient(135deg,#3b82f6,#2563eb);';
|
||||
@endphp
|
||||
<div class="mob-row" style="{{ !$isActive ? 'opacity:.5;' : '' }}">
|
||||
<div style="display:flex;align-items:center;gap:10px;">
|
||||
<div class="av-wrap">
|
||||
<div class="av" style="{{ $avBg }}">{{ $initial }}</div>
|
||||
@if($isActive)<div class="av-dot"></div>@endif
|
||||
</div>
|
||||
<div style="flex:1;min-width:0;">
|
||||
<div style="display:flex;align-items:center;gap:6px;flex-wrap:wrap;">
|
||||
<span class="mob-name">{{ $u->name }}</span>
|
||||
@if($isMe)
|
||||
<span style="font-size:9.5px;padding:1px 7px;border-radius:20px;background:#ede9fe;color:#6d28d9;border:1px solid #ddd6fe;font-weight:700;">Kamu</span>
|
||||
@endif
|
||||
</div>
|
||||
<div class="mob-email">{{ $u->email }}</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mob-meta">
|
||||
@if($isAdmin)
|
||||
<span class="badge" style="background:#f0fdf4;color:#166534;border:1px solid #bbf7d0;"><span class="dot" style="background:#10b981;"></span>Admin</span>
|
||||
@else
|
||||
<span class="badge" style="background:#eff6ff;color:#1d4ed8;border:1px solid #bfdbfe;"><span class="dot" style="background:#3b82f6;"></span>RT</span>
|
||||
@endif
|
||||
|
||||
@if($isActive)
|
||||
<span class="badge" style="background:#f0fdf4;color:#166534;border:1px solid #bbf7d0;"><span class="dot" style="background:#10b981;"></span>Aktif</span>
|
||||
@else
|
||||
<span class="badge" style="background:#f3f4f6;color:#6b7280;border:1px solid #e5e7eb;"><span class="dot" style="background:#9ca3af;"></span>Nonaktif</span>
|
||||
@endif
|
||||
|
||||
<span style="font-size:10.5px;color:#94a3b8;">ID #{{ $u->id }}</span>
|
||||
</div>
|
||||
|
||||
<div class="mob-actions">
|
||||
@if($isMe)
|
||||
<span class="btn-a btn-locked">
|
||||
<svg fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 15v2m-6 4h12a2 2 0 002-2v-6a2 2 0 00-2-2H6a2 2 0 00-2 2v6a2 2 0 002 2zm10-10V7a4 4 0 00-8 0v4h8z"/></svg>
|
||||
Terkunci
|
||||
</span>
|
||||
@else
|
||||
<form method="POST" action="{{ route('admin.data-akun.role',$u->id) }}">
|
||||
@csrf
|
||||
<input type="hidden" name="role" value="{{ $newRole }}">
|
||||
<button type="submit" class="btn-a btn-role"
|
||||
onclick="return confirm('Ubah role {{ addslashes($u->name) }} menjadi {{ $newRoleLabel }}?')">
|
||||
<svg fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 7h12m0 0l-4-4m4 4l-4 4m0 6H4m0 0l4 4m-4-4l4-4"/></svg>
|
||||
→ {{ $newRoleLabel }}
|
||||
</button>
|
||||
</form>
|
||||
|
||||
<form method="POST" action="{{ route('admin.data-akun.toggle-aktif',$u->id) }}">
|
||||
@csrf
|
||||
<button type="submit"
|
||||
class="btn-a {{ $isActive ? 'btn-nonaktif' : 'btn-aktif' }}"
|
||||
onclick="return confirm('{{ $isActive ? 'Nonaktifkan' : 'Aktifkan' }} akun {{ addslashes($u->name) }}?')">
|
||||
{{ $isActive ? 'Nonaktifkan' : 'Aktifkan' }}
|
||||
</button>
|
||||
</form>
|
||||
|
||||
<form method="POST" action="{{ route('admin.data-akun.hapus',$u->id) }}"
|
||||
onsubmit="return confirm('Yakin hapus akun {{ addslashes($u->name) }}?')">
|
||||
@csrf @method('DELETE')
|
||||
<button type="submit" class="btn-a 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>
|
||||
</div>
|
||||
@empty
|
||||
<div style="padding:40px 16px;text-align:center;">
|
||||
<p style="font-size:13px;font-weight:600;color:#6b7280;">Belum ada akun</p>
|
||||
</div>
|
||||
@endforelse
|
||||
</div>
|
||||
|
||||
{{-- PAGINATION --}}
|
||||
@if(isset($users) && method_exists($users,'links') && $users->hasPages())
|
||||
<div style="padding:10px 16px;border-top:1px solid #f1f5f9;background:#fafafa;">
|
||||
{{ $users->appends(request()->query())->links() }}
|
||||
</div>
|
||||
@endif
|
||||
|
||||
{{-- WATERMARK --}}
|
||||
<div style="padding:12px 16px;border-top:1px solid #f1f5f9;display:flex;align-items:center;justify-content:center;gap:8px;">
|
||||
<svg width="13" height="13" fill="#d1d5db" 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 style="font-size:11px;color:#d1d5db;font-weight:500;">SiBantuDes · Desa Ngerong</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
@keyframes pulse{0%,100%{opacity:1;}50%{opacity:.5;}}
|
||||
</style>
|
||||
|
||||
</x-app-layout>
|
||||
|
|
@ -1,138 +1,137 @@
|
|||
<x-app-layout>
|
||||
|
||||
<x-slot name="header">
|
||||
<div style="display:flex;align-items:center;justify-content:space-between;gap:16px;">
|
||||
<div style="display:flex;align-items:center;justify-content:space-between;gap:12px;flex-wrap:wrap;">
|
||||
<div>
|
||||
<h2 style="font-size:17px;font-weight:800;color:#111827;line-height:1.2;">Data Warga</h2>
|
||||
<p style="font-size:11px;color:#9ca3af;margin-top:2px;">Kelola dan pantau seluruh data warga dari semua dusun</p>
|
||||
<h2 style="font-size:16px;font-weight:800;color:#0f172a;letter-spacing:-.02em;">Data Warga</h2>
|
||||
<p style="font-size:11px;color:#94a3b8;margin-top:2px;">Kelola dan pantau seluruh data warga dari semua dusun</p>
|
||||
</div>
|
||||
<div style="display:inline-flex;align-items:center;gap:6px;background:#f8fafc;border:1.5px solid #e5e7eb;border-radius:10px;padding:6px 12px;font-size:11px;color:#6b7280;">
|
||||
<div style="display:inline-flex;align-items:center;gap:6px;background:#f8fafc;border:1.5px solid #e2e8f0;border-radius:10px;padding:6px 12px;font-size:11px;color:#64748b;white-space:nowrap;">
|
||||
<svg width="13" height="13" fill="none" stroke="#3b82f6" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 7V3m8 4V3m-9 8h10M5 21h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v12a2 2 0 002 2z"/></svg>
|
||||
{{ \Carbon\Carbon::now('Asia/Jakarta')->translatedFormat('l, d F Y') }} — {{ \Carbon\Carbon::now('Asia/Jakarta')->format('H:i') }} WIB
|
||||
{{ \Carbon\Carbon::now('Asia/Jakarta')->translatedFormat('d F Y') }} — {{ \Carbon\Carbon::now('Asia/Jakarta')->format('H:i') }} WIB
|
||||
</div>
|
||||
</div>
|
||||
</x-slot>
|
||||
|
||||
<style>
|
||||
.fl-box{display:flex;align-items:center;gap:9px;border-radius:12px;padding:9px 14px;margin-bottom:10px;font-size:12px;font-weight:500;}
|
||||
.dw-card{background:#fff;border-radius:16px;border:1.5px solid #f1f5f9;box-shadow:0 1px 4px rgba(0,0,0,.04),0 4px 16px rgba(0,0,0,.03);overflow:hidden;}
|
||||
.fl-box{display:flex;align-items:center;gap:9px;border-radius:12px;padding:10px 14px;font-size:12px;font-weight:500;}
|
||||
.fl-box svg{width:14px;height:14px;flex-shrink:0;}
|
||||
.dw-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;}
|
||||
.sw{position:relative;}
|
||||
.sw svg{position:absolute;left:11px;top:50%;transform:translateY(-50%);width:14px;height:14px;stroke:#9ca3af;pointer-events:none;}
|
||||
.sw input{width:100%;padding:9px 12px 9px 34px;font-size:13px;color:#111827;background:#f9fafb;border:1.5px solid #e5e7eb;border-radius:11px;outline:none;transition:border-color .15s,box-shadow .15s;}
|
||||
.sw input{width:100%;padding:9px 12px 9px 34px;font-size:13px;color:#111827;background:#f9fafb;border:1.5px solid #e5e7eb;border-radius:11px;outline:none;transition:border-color .15s,box-shadow .15s;font-family:inherit;}
|
||||
.sw input:focus{border-color:#3b82f6;background:#fff;box-shadow:0 0 0 3px rgba(59,130,246,.10);}
|
||||
.sw input::placeholder{color:#9ca3af;}
|
||||
table{width:100%;border-collapse:collapse;}
|
||||
.tbl-wrap{overflow-x:auto;-webkit-overflow-scrolling:touch;}
|
||||
table{width:100%;border-collapse:collapse;min-width:700px;}
|
||||
thead tr{background:#f8fafc;border-bottom:1.5px solid #f1f5f9;}
|
||||
thead th{padding:9px 13px;text-align:left;font-size:10px;font-weight:700;color:#9ca3af;text-transform:uppercase;letter-spacing:.08em;white-space:nowrap;}
|
||||
thead th{padding:9px 12px;text-align:left;font-size:9.5px;font-weight:700;color:#94a3b8;text-transform:uppercase;letter-spacing:.07em;white-space:nowrap;}
|
||||
tbody tr{border-bottom:1px solid #f8fafc;transition:background .1s;}
|
||||
tbody tr:hover{background:#f0f7ff;}
|
||||
tbody tr:last-child{border-bottom:none;}
|
||||
tbody td{padding:10px 13px;vertical-align:middle;}
|
||||
.av{width:32px;height:32px;border-radius:10px;background:linear-gradient(135deg,#3b82f6,#2563eb);display:flex;align-items:center;justify-content:center;font-size:12px;font-weight:800;color:#fff;flex-shrink:0;}
|
||||
.nik-pill{font-family:monospace;font-size:10.5px;color:#6b7280;background:#f3f4f6;padding:2px 7px;border-radius:6px;}
|
||||
.rt-badge{display:inline-flex;align-items:center;padding:2px 7px;border-radius:6px;background:#eff6ff;color:#1d4ed8;font-size:10.5px;font-weight:700;border:1px solid #bfdbfe;margin-top:3px;}
|
||||
tbody td{padding:10px 12px;vertical-align:middle;}
|
||||
.av{width:30px;height:30px;border-radius:9px;background:linear-gradient(135deg,#3b82f6,#2563eb);display:flex;align-items:center;justify-content:center;font-size:11px;font-weight:800;color:#fff;flex-shrink:0;}
|
||||
.nik-pill{font-family:monospace;font-size:10px;color:#6b7280;background:#f3f4f6;padding:2px 6px;border-radius:6px;white-space:nowrap;}
|
||||
.rt-badge{display:inline-flex;align-items:center;padding:2px 7px;border-radius:6px;background:#eff6ff;color:#1d4ed8;font-size:10px;font-weight:700;border:1px solid #bfdbfe;margin-top:2px;}
|
||||
.badge{display:inline-flex;align-items:center;gap:4px;padding:3px 9px;border-radius:20px;font-size:10.5px;font-weight:700;white-space:nowrap;}
|
||||
.dot{width:5px;height:5px;border-radius:50%;flex-shrink:0;}
|
||||
.trk-terkirim{background:#eff6ff;color:#1d4ed8;} .trk-terkirim .dot{background:#3b82f6;}
|
||||
.trk-validasi{background:#fffbeb;color:#b45309;} .trk-validasi .dot{background:#f59e0b;}
|
||||
.trk-selesai {background:#f0fdf4;color:#166534;} .trk-selesai .dot{background:#22c55e;}
|
||||
.trk-default {background:#f3f4f6;color:#6b7280;} .trk-default .dot{background:#9ca3af;}
|
||||
|
||||
.stat-pending{background:#fffbeb;color:#b45309;border:1px solid #fde68a;}
|
||||
.trk-terkirim{background:#eff6ff;color:#1d4ed8;}.trk-terkirim .dot{background:#3b82f6;}
|
||||
.trk-validasi{background:#fffbeb;color:#b45309;}.trk-validasi .dot{background:#f59e0b;}
|
||||
.trk-selesai{background:#f0fdf4;color:#166534;}.trk-selesai .dot{background:#22c55e;}
|
||||
.trk-default{background:#f3f4f6;color:#6b7280;}.trk-default .dot{background:#9ca3af;}
|
||||
.stat-yes{background:#f0fdf4;color:#166534;border:1px solid #bbf7d0;}
|
||||
.stat-no{background:#fff1f2;color:#9f1239;border:1px solid #fecdd3;}
|
||||
|
||||
.btn-act{display:inline-flex;align-items:center;gap:4px;padding:4px 9px;border-radius:8px;font-size:10.5px;font-weight:600;text-decoration:none;border:none;cursor:pointer;white-space:nowrap;line-height:1.4;transition:filter .1s;}
|
||||
.stat-pending{background:#fffbeb;color:#b45309;border:1px solid #fde68a;}
|
||||
.prob-wrap{display:flex;align-items:center;gap:6px;}
|
||||
.prob-bg{flex:1;height:5px;background:#f1f5f9;border-radius:99px;overflow:hidden;min-width:50px;}
|
||||
.prob-bar{height:100%;border-radius:99px;}
|
||||
.btn-act{display:inline-flex;align-items:center;gap:4px;padding:4px 9px;border-radius:8px;font-size:10.5px;font-weight:600;text-decoration:none;border:none;cursor:pointer;white-space:nowrap;line-height:1.4;transition:filter .1s;font-family:inherit;}
|
||||
.btn-act:hover{filter:brightness(.92);}
|
||||
.btn-act svg{width:11px;height:11px;flex-shrink:0;}
|
||||
.btn-detail{background:#f3f4f6;color:#374151;}
|
||||
.btn-validasi{background:#eff6ff;color:#1d4ed8;}
|
||||
.btn-kirim{background:#fffbeb;color:#b45309;}
|
||||
.btn-reset{display:inline-flex;align-items:center;gap:6px;padding:9px 14px;background:#ef4444;color:#fff;font-size:12px;font-weight:700;border:none;border-radius:11px;cursor:pointer;box-shadow:0 2px 8px rgba(239,68,68,.22);white-space:nowrap;}
|
||||
.btn-reset:hover{filter:brightness(.95);}
|
||||
|
||||
.info-chip{display:inline-flex;align-items:center;gap:5px;padding:4px 10px;border-radius:999px;font-size:10.5px;font-weight:700;background:#f8fafc;color:#475569;border:1px solid #e2e8f0;}
|
||||
|
||||
.prob-wrap{display:flex;align-items:center;gap:7px;}
|
||||
.prob-bg{flex:1;height:5px;background:#f1f5f9;border-radius:99px;overflow:hidden;}
|
||||
.prob-bar{height:100%;border-radius:99px;}
|
||||
|
||||
/* ── MODAL FIX — scroll handled by .m-wrap, not .m-body ── */
|
||||
.btn-reset{display:inline-flex;align-items:center;gap:6px;padding:8px 14px;background:#ef4444;color:#fff;font-size:12px;font-weight:700;border:none;border-radius:10px;cursor:pointer;white-space:nowrap;font-family:inherit;}
|
||||
.btn-reset:hover{filter:brightness(.93);}
|
||||
.info-chip{display:inline-flex;align-items:center;gap:4px;padding:3px 8px;border-radius:99px;font-size:10px;font-weight:700;background:#f8fafc;color:#475569;border:1px solid #e2e8f0;white-space:nowrap;}
|
||||
.m-backdrop{position:fixed;inset:0;z-index:40;background:rgba(0,0,0,.45);backdrop-filter:blur(3px);}
|
||||
.m-wrap{position:fixed;inset:0;z-index:50;overflow-y:auto;pointer-events:none;display:flex;align-items:flex-start;justify-content:center;padding:24px 16px;}
|
||||
.m-box{width:100%;max-width:620px;background:#fff;border-radius:22px;box-shadow:0 24px 64px rgba(0,0,0,.18);overflow:hidden;display:flex;flex-direction:column;pointer-events:auto;margin:auto;}
|
||||
.mhero{position:relative;overflow:hidden;padding:18px 22px;background:linear-gradient(135deg,#1e40af 0%,#2563eb 55%,#3b82f6 100%);}
|
||||
.m-wrap{position:fixed;inset:0;z-index:50;overflow-y:auto;display:flex;align-items:flex-start;justify-content:center;padding:20px 12px 32px;}
|
||||
.m-box{width:100%;max-width:600px;background:#fff;border-radius:20px;box-shadow:0 24px 64px rgba(0,0,0,.18);overflow:hidden;margin:auto;}
|
||||
.mhero{position:relative;overflow:hidden;padding:16px 18px;background:linear-gradient(135deg,#1e40af,#2563eb,#3b82f6);}
|
||||
.mhero-b1{position:absolute;top:-20px;right:-20px;width:90px;height:90px;background:rgba(255,255,255,.08);border-radius:50%;}
|
||||
.mhero-b2{position:absolute;bottom:-22px;left:35%;width:70px;height:70px;background:rgba(255,255,255,.06);border-radius:50%;}
|
||||
.mhero-row{position:relative;z-index:1;display:flex;align-items:center;justify-content:space-between;gap:12px;}
|
||||
.m-av{width:44px;height:44px;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:18px;font-weight:900;color:#fff;flex-shrink:0;}
|
||||
.m-title{font-size:15px;font-weight:800;color:#fff;line-height:1.2;}
|
||||
.mhero-row{position:relative;z-index:1;display:flex;align-items:center;justify-content:space-between;gap:10px;}
|
||||
.m-av{width:42px;height:42px;border-radius:12px;background:rgba(255,255,255,.18);border:2px solid rgba(255,255,255,.25);display:flex;align-items:center;justify-content:center;font-size:17px;font-weight:900;color:#fff;flex-shrink:0;}
|
||||
.m-title{font-size:14px;font-weight:800;color:#fff;line-height:1.2;}
|
||||
.m-sub{font-size:11px;color:rgba(191,219,254,.85);margin-top:2px;}
|
||||
.m-close{width:30px;height:30px;border-radius:9px;background:rgba(255,255,255,.18);border:none;cursor:pointer;display:flex;align-items:center;justify-content:center;color:#fff;flex-shrink:0;}
|
||||
.m-close:hover{background:rgba(255,255,255,.28);}
|
||||
.m-close svg{width:14px;height:14px;stroke:currentColor;}
|
||||
.m-st-pill{margin-top:10px;position:relative;display:inline-flex;align-items:center;gap:5px;padding:4px 11px;border-radius:20px;background:rgba(255,255,255,.18);color:#fff;border:1px solid rgba(255,255,255,.25);font-size:11px;font-weight:700;}
|
||||
.m-body{overflow-y:visible;flex:1;padding:14px;background:#f8fafc;display:flex;flex-direction:column;gap:10px;}
|
||||
.msec{background:#fff;border-radius:14px;border:1.5px solid #f1f5f9;overflow:hidden;}
|
||||
.m-st-pill{margin-top:10px;position:relative;z-index:1;display:inline-flex;align-items:center;gap:5px;padding:4px 11px;border-radius:20px;background:rgba(255,255,255,.18);color:#fff;border:1px solid rgba(255,255,255,.25);font-size:11px;font-weight:700;}
|
||||
.m-body{padding:12px;background:#f8fafc;display:flex;flex-direction:column;gap:10px;}
|
||||
.msec{background:#fff;border-radius:13px;border:1.5px solid #f1f5f9;overflow:hidden;}
|
||||
.msec-h{padding:8px 14px;border-bottom:1px solid #f1f5f9;background:#fafafa;display:flex;align-items:center;gap:7px;}
|
||||
.msec-h .bar{width:3px;height:13px;border-radius:3px;flex-shrink:0;}
|
||||
.msec-h h4{font-size:10px;font-weight:700;color:#6b7280;text-transform:uppercase;letter-spacing:.08em;}
|
||||
.msec-b{padding:12px 14px;}
|
||||
.mg2{display:grid;grid-template-columns:1fr 1fr;gap:10px 20px;}
|
||||
.mg2{display:grid;grid-template-columns:1fr 1fr;gap:10px 16px;}
|
||||
.mlbl{font-size:10px;color:#9ca3af;text-transform:uppercase;letter-spacing:.07em;margin-bottom:2px;}
|
||||
.mval{font-size:12.5px;font-weight:600;color:#111827;}
|
||||
.mval-mono{font-family:monospace;font-size:11px;background:#f3f4f6;border:1px solid #e5e7eb;border-radius:7px;padding:3px 8px;color:#374151;display:inline-block;}
|
||||
.m-footer{padding:11px 16px;border-top:1px solid #f1f5f9;background:#fff;flex-shrink:0;display:flex;justify-content:flex-end;}
|
||||
.btn-close-m{display:inline-flex;align-items:center;gap:6px;padding:8px 18px;font-size:12.5px;font-weight:700;background:#f3f4f6;color:#374151;border:none;border-radius:10px;cursor:pointer;}
|
||||
.mval-mono{font-family:monospace;font-size:11px;background:#f3f4f6;border:1px solid #e5e7eb;border-radius:7px;padding:3px 8px;color:#374151;display:inline-block;word-break:break-all;}
|
||||
.m-footer{padding:10px 16px;border-top:1px solid #f1f5f9;background:#fff;display:flex;justify-content:flex-end;}
|
||||
.btn-close-m{display:inline-flex;align-items:center;gap:6px;padding:8px 18px;font-size:12.5px;font-weight:700;background:#f3f4f6;color:#374151;border:none;border-radius:10px;cursor:pointer;font-family:inherit;}
|
||||
.btn-close-m:hover{background:#e5e7eb;}
|
||||
@media(max-width:640px){
|
||||
.mg2{grid-template-columns:1fr;}
|
||||
.mg2 [style*="grid-column:span 2"]{grid-column:span 1 !important;}
|
||||
.m-wrap{padding:12px 8px 24px;}
|
||||
.m-box{border-radius:16px;}
|
||||
.msec-b{padding:10px 12px;}
|
||||
}
|
||||
</style>
|
||||
|
||||
{{-- FLASH --}}
|
||||
@if(session('success'))
|
||||
<div class="fl-box" style="background:#f0fdf4;border:1.5px solid #bbf7d0;color:#166534;">
|
||||
<div class="fl-box" style="background:#f0fdf4;border:1.5px solid #bbf7d0;color:#166534;margin-bottom:10px;">
|
||||
<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="fl-box" style="background:#fff1f2;border:1.5px solid #fecdd3;color:#9f1239;">
|
||||
<div class="fl-box" style="background:#fff1f2;border:1.5px solid #fecdd3;color:#9f1239;margin-bottom:10px;">
|
||||
<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
|
||||
|
||||
{{-- SEARCH --}}
|
||||
<div class="dw-card" style="margin-bottom:12px;padding:14px 16px;">
|
||||
<div class="dw-card" style="padding:14px 16px;">
|
||||
<div style="display:flex;align-items:center;gap:7px;margin-bottom:10px;">
|
||||
<div style="width:3px;height:14px;background:#2563eb;border-radius:4px;"></div>
|
||||
<h3 style="font-size:12px;font-weight:700;color:#374151;">Pencarian</h3>
|
||||
<h3 style="font-size:12px;font-weight:700;color:#374151;">Pencarian & Filter</h3>
|
||||
</div>
|
||||
|
||||
<form method="GET" action="{{ url()->current() }}" style="display:flex;gap:8px;align-items:center;flex-wrap:wrap;">
|
||||
<div class="sw" style="flex:1;min-width:260px;">
|
||||
<form method="GET" action="{{ url()->current() }}"
|
||||
style="display:flex;gap:8px;align-items:center;flex-wrap:wrap;">
|
||||
<div class="sw" style="flex:1;min-width:200px;">
|
||||
<svg fill="none" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z"/></svg>
|
||||
<input type="text" name="q" value="{{ request('q') }}" placeholder="Cari NIK, Nama, atau Email...">
|
||||
</div>
|
||||
|
||||
<button type="submit" style="display:inline-flex;align-items:center;gap:6px;padding:9px 16px;background:#2563eb;color:#fff;font-size:12px;font-weight:700;border:none;border-radius:11px;cursor:pointer;box-shadow:0 2px 8px rgba(37,99,235,.25);white-space:nowrap;">
|
||||
<button type="submit"
|
||||
style="display:inline-flex;align-items:center;gap:6px;padding:9px 16px;background:#2563eb;color:#fff;font-size:12px;font-weight:700;border:none;border-radius:11px;cursor:pointer;white-space:nowrap;font-family:inherit;">
|
||||
<svg width="13" height="13" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z"/></svg>
|
||||
Cari
|
||||
</button>
|
||||
|
||||
@if(request()->filled('q'))
|
||||
<a href="{{ url()->current() }}" style="display:inline-flex;align-items:center;justify-content:center;width:36px;height:36px;background:#f3f4f6;border-radius:10px;color:#6b7280;text-decoration:none;" title="Hapus">
|
||||
<a href="{{ url()->current() }}"
|
||||
style="display:inline-flex;align-items:center;justify-content:center;width:36px;height:36px;background:#f3f4f6;border-radius:10px;color:#6b7280;text-decoration:none;">
|
||||
<svg width="14" height="14" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12"/></svg>
|
||||
</a>
|
||||
@endif
|
||||
</form>
|
||||
|
||||
<div style="margin-top:12px;display:flex;justify-content:flex-end;">
|
||||
<form method="POST"
|
||||
action="{{ route('admin.data-warga.bersihkan') }}"
|
||||
<form method="POST" action="{{ route('admin.data-warga.bersihkan') }}"
|
||||
onsubmit="return confirm('Yakin ingin menghapus semua data warga aktif? Data pada laporan arsip tidak akan terhapus.');">
|
||||
@csrf
|
||||
@method('DELETE')
|
||||
@csrf @method('DELETE')
|
||||
<button type="submit" class="btn-reset">
|
||||
<svg width="13" height="13" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16"/>
|
||||
|
|
@ -143,11 +142,10 @@
|
|||
</div>
|
||||
</div>
|
||||
|
||||
{{-- TABLE --}}
|
||||
<div class="dw-card">
|
||||
<div style="padding:10px 16px;border-bottom:1px solid #f1f5f9;background:#fafafa;display:flex;align-items:center;justify-content:space-between;">
|
||||
<div style="padding:10px 16px;border-bottom:1px solid #f1f5f9;background:#fafafa;display:flex;align-items:center;justify-content:space-between;flex-wrap:wrap;gap:6px;">
|
||||
<div style="display:flex;align-items:center;gap:7px;">
|
||||
<div style="width:3px;height:16px;background:#2563eb;border-radius:4px;"></div>
|
||||
<div style="width:3px;height:15px;background:#2563eb;border-radius:4px;"></div>
|
||||
<span style="font-size:12.5px;font-weight:700;color:#374151;">
|
||||
Daftar Warga
|
||||
@if(isset($wargas) && method_exists($wargas,'total'))
|
||||
|
|
@ -160,17 +158,13 @@
|
|||
@endif
|
||||
</div>
|
||||
|
||||
<div style="overflow-x:auto;">
|
||||
<div class="tbl-wrap">
|
||||
<table>
|
||||
<colgroup>
|
||||
<col style="width:13%"><col style="width:20%"><col style="width:12%">
|
||||
<col style="width:13%"><col style="width:13%"><col style="width:11%"><col style="width:18%">
|
||||
</colgroup>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>NIK</th>
|
||||
<th>Nama</th>
|
||||
<th>Dusun / RT</th>
|
||||
<th>Nama / Pekerjaan</th>
|
||||
<th>Dusun · RT</th>
|
||||
<th>Penghasilan</th>
|
||||
<th>Probabilitas</th>
|
||||
<th>Tracking</th>
|
||||
|
|
@ -188,57 +182,56 @@
|
|||
if(strtolower($warga->pekerjaan??'')==='tidak bekerja') $positive[]='Tidak bekerja';
|
||||
elseif(in_array(strtolower($warga->pekerjaan??''),['buruh harian','buruh','petani','nelayan'])) $positive[]='Pekerjaan tergolong rentan';
|
||||
else $negative[]='Memiliki pekerjaan relatif stabil';
|
||||
|
||||
if(($warga->penghasilan??0)<=1000000) $positive[]='Penghasilan rendah';
|
||||
elseif(($warga->penghasilan??0)<=2000000) $positive[]='Penghasilan tergolong rendah';
|
||||
else $negative[]='Penghasilan relatif tinggi';
|
||||
|
||||
if(($warga->jumlah_tanggungan??0)>=4) $positive[]='Tanggungan banyak';
|
||||
elseif(($warga->jumlah_tanggungan??0)>=2) $positive[]='Tanggungan cukup banyak';
|
||||
else $negative[]='Tanggungan sedikit';
|
||||
|
||||
$aset=strtolower($warga->aset_kepemilikan??'');
|
||||
if(str_contains($aset,'mobil')) $negative[]='Memiliki aset mobil';
|
||||
if(str_contains($aset,'motor')) $negative[]='Memiliki kendaraan bermotor';
|
||||
if(str_contains($aset,'rumah')) $negative[]='Memiliki aset rumah';
|
||||
if(in_array($aset,['tidak ada','-','tidak punya'])) $positive[]='Tidak memiliki aset berarti';
|
||||
|
||||
if(strtolower($warga->bantuan_lain??'')==='ya') $negative[]='Sudah menerima bantuan lain';
|
||||
else $positive[]='Belum menerima bantuan lain';
|
||||
|
||||
if(($warga->usia??0)>=60) $positive[]='Usia lanjut';
|
||||
elseif(($warga->usia??0)>=45) $positive[]='Usia cukup rentan';
|
||||
|
||||
$summary='Nilai kelayakan dihitung berdasarkan pekerjaan, penghasilan, tanggungan, aset, bantuan lain, dan usia.';
|
||||
// Parameter baru
|
||||
if(strtolower($warga->kondisi_rumah??'')==='tidak layak') $positive[]='Kondisi rumah tidak layak huni';
|
||||
elseif(strtolower($warga->kondisi_rumah??'')==='sedang') $positive[]='Kondisi rumah sedang';
|
||||
else $negative[]='Kondisi rumah layak';
|
||||
if(($warga->meteran_listrik??'')==='450VA') $positive[]='Meteran listrik 450VA';
|
||||
elseif(($warga->meteran_listrik??'')==='900VA') $positive[]='Meteran listrik 900VA';
|
||||
else $negative[]='Meteran listrik 1300VA ke atas';
|
||||
if(strtolower($warga->sumber_air??'')==='sungai') $positive[]='Sumber air sungai';
|
||||
elseif(strtolower($warga->sumber_air??'')==='sumur') $positive[]='Sumber air sumur';
|
||||
else $negative[]='Sumber air PDAM';
|
||||
|
||||
$summary='Nilai kelayakan dihitung berdasarkan pekerjaan, penghasilan, tanggungan, aset, bantuan lain, usia, kondisi rumah, meteran listrik, dan sumber air.';
|
||||
if(count($positive)&&count($negative)) $summary.=' Terdapat faktor pendukung dan pengurang.';
|
||||
elseif(count($positive)) $summary.=' Faktor mayoritas mendukung kelayakan.';
|
||||
else $summary.=' Faktor mayoritas menurunkan kelayakan.';
|
||||
|
||||
$dp=[
|
||||
'nik'=>$warga->nik,
|
||||
'no_kk'=>$warga->no_kk??'-',
|
||||
'nama_lengkap'=>$warga->nama_lengkap,
|
||||
'jenis_kelamin'=>$warga->jenis_kelamin??'-',
|
||||
'nik'=>$warga->nik,'no_kk'=>$warga->no_kk??'-',
|
||||
'nama_lengkap'=>$warga->nama_lengkap,'jenis_kelamin'=>$warga->jenis_kelamin??'-',
|
||||
'tempat_lahir'=>$warga->tempat_lahir??'-',
|
||||
'tanggal_lahir'=>$warga->tanggal_lahir?\Carbon\Carbon::parse($warga->tanggal_lahir)->translatedFormat('d F Y'):'-',
|
||||
'usia'=>$warga->usia??'-',
|
||||
'status_perkawinan'=>$warga->status_perkawinan??'-',
|
||||
'alamat'=>$warga->alamat??'-',
|
||||
'desa'=>$warga->desa??'-',
|
||||
'usia'=>$warga->usia??'-','status_perkawinan'=>$warga->status_perkawinan??'-',
|
||||
'alamat'=>$warga->alamat??'-','desa'=>$warga->desa??'-',
|
||||
'aset_kepemilikan'=>$warga->aset_kepemilikan??'-',
|
||||
'pekerjaan'=>$warga->pekerjaan??'-',
|
||||
'penghasilan'=>$warga->penghasilan??0,
|
||||
'jumlah_tanggungan'=>$warga->jumlah_tanggungan??'-',
|
||||
'bantuan_lain'=>$warga->bantuan_lain??'-',
|
||||
'status_verifikasi'=>$warga->status_verifikasi??'-',
|
||||
'tracking_status'=>$warga->tracking_status??'-',
|
||||
'dusun'=>$warga->rt->dusun->nama_dusun??'-',
|
||||
'rt'=>$warga->rt->nomor_rt??'-',
|
||||
'kondisi_rumah'=>$warga->kondisi_rumah??'-',
|
||||
'meteran_listrik'=>$warga->meteran_listrik??'-',
|
||||
'sumber_air'=>$warga->sumber_air??'-',
|
||||
'pekerjaan'=>$warga->pekerjaan??'-','penghasilan'=>$warga->penghasilan??0,
|
||||
'jumlah_tanggungan'=>$warga->jumlah_tanggungan??'-','bantuan_lain'=>$warga->bantuan_lain??'-',
|
||||
'status_verifikasi'=>$warga->status_verifikasi??'-','tracking_status'=>$warga->tracking_status??'-',
|
||||
'dusun'=>$warga->rt->dusun->nama_dusun??'-','rt'=>$warga->rt->nomor_rt??'-',
|
||||
'probability'=>optional($warga->prediksiKelayakan)->probability,
|
||||
'recommendation'=>optional($warga->prediksiKelayakan)->recommendation,
|
||||
'positive'=>$positive,
|
||||
'negative'=>$negative,
|
||||
'summary'=>$summary,
|
||||
'positive'=>$positive,'negative'=>$negative,'summary'=>$summary,
|
||||
'input_oleh'=>optional($warga->user)->name??(optional($warga->user)->email??'-'),
|
||||
'created_at'=>optional($warga->created_at)->translatedFormat('d F Y, H:i')??'-'
|
||||
];
|
||||
|
|
@ -246,37 +239,34 @@
|
|||
@endphp
|
||||
<tr>
|
||||
<td><span class="nik-pill">{{ $warga->nik }}</span></td>
|
||||
|
||||
<td>
|
||||
<div style="display:flex;align-items:center;gap:9px;">
|
||||
<div style="display:flex;align-items:center;gap:8px;">
|
||||
<div class="av">{{ strtoupper(substr($warga->nama_lengkap,0,1)) }}</div>
|
||||
<div style="min-width:0;">
|
||||
<div style="font-size:12.5px;font-weight:600;color:#111827;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;">{{ $warga->nama_lengkap }}</div>
|
||||
<div style="font-size:12px;font-weight:600;color:#111827;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;max-width:140px;">{{ $warga->nama_lengkap }}</div>
|
||||
<div style="font-size:10.5px;color:#9ca3af;margin-top:1px;">{{ $warga->pekerjaan??'-' }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</td>
|
||||
|
||||
<td>
|
||||
<div style="font-size:12px;font-weight:600;color:#374151;">{{ $warga->rt->dusun->nama_dusun??'-' }}</div>
|
||||
<div style="font-size:11.5px;font-weight:600;color:#374151;white-space:nowrap;">{{ $warga->rt->dusun->nama_dusun??'-' }}</div>
|
||||
<span class="rt-badge">RT {{ str_pad($warga->rt->nomor_rt??'-',3,'0',STR_PAD_LEFT) }}</span>
|
||||
</td>
|
||||
|
||||
<td style="font-size:12.5px;font-weight:600;color:#374151;">
|
||||
<td style="font-size:12px;font-weight:600;color:#374151;white-space:nowrap;">
|
||||
Rp {{ number_format($warga->penghasilan??0,0,',','.') }}
|
||||
</td>
|
||||
|
||||
<td>
|
||||
@if($probPct !== null)
|
||||
<div class="prob-wrap">
|
||||
<div class="prob-bg"><div class="prob-bar" style="width:{{ min($probPct,100) }}%;background:{{ $sc }};"></div></div>
|
||||
<div class="prob-bg">
|
||||
<div class="prob-bar" style="width:{{ min($probPct,100) }}%;background:{{ $sc }};"></div>
|
||||
</div>
|
||||
<span style="font-size:11px;font-weight:700;color:{{ $sc }};white-space:nowrap;">{{ number_format($probPct,1) }}%</span>
|
||||
</div>
|
||||
@else
|
||||
<span style="font-size:12px;color:#d1d5db;">—</span>
|
||||
@endif
|
||||
</td>
|
||||
|
||||
<td>
|
||||
@if($trk==='terkirim')
|
||||
<span class="badge trk-terkirim"><span class="dot"></span>Terkirim</span>
|
||||
|
|
@ -288,14 +278,12 @@
|
|||
<span class="badge trk-default"><span class="dot"></span>—</span>
|
||||
@endif
|
||||
</td>
|
||||
|
||||
<td>
|
||||
<div style="display:flex;align-items:center;justify-content:flex-end;gap:4px;flex-wrap:wrap;">
|
||||
<button type="button" onclick="openDetailModal({{ $warga->id }})" class="btn-act btn-detail">
|
||||
<svg fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 12a3 3 0 11-6 0 3 3 0 016 0z"/><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M2.458 12C3.732 7.943 7.523 5 12 5c4.478 0 8.268 2.943 9.542 7-1.274 4.057-5.064 7-9.542 7-4.477 0-8.268-2.943-9.542-7z"/></svg>
|
||||
Detail
|
||||
</button>
|
||||
|
||||
@if($trk==='terkirim')
|
||||
<form method="POST" action="{{ route('admin.data-warga.mulai-validasi',$warga->id) }}">
|
||||
@csrf
|
||||
|
|
@ -305,20 +293,17 @@
|
|||
</button>
|
||||
</form>
|
||||
@endif
|
||||
|
||||
@if($trk==='sedang_validasi')
|
||||
<span class="info-chip">Lanjutkan lewat filterisasi</span>
|
||||
<span class="info-chip">Lanjut filterisasi</span>
|
||||
@endif
|
||||
|
||||
@if($trk==='selesai')
|
||||
@if(($warga->status_verifikasi ?? '') === 'disetujui')
|
||||
@if(($warga->status_verifikasi??'') === 'disetujui')
|
||||
<span class="badge stat-yes"><span class="dot" style="background:#22c55e;"></span>Diterima</span>
|
||||
@elseif(($warga->status_verifikasi ?? '') === 'ditolak')
|
||||
<span class="badge stat-no"><span class="dot" style="background:#f43f5e;"></span>Tidak Diterima</span>
|
||||
@elseif(($warga->status_verifikasi??'') === 'ditolak')
|
||||
<span class="badge stat-no"><span class="dot" style="background:#f43f5e;"></span>Ditolak</span>
|
||||
@else
|
||||
<span class="badge stat-pending"><span class="dot" style="background:#f59e0b;"></span>Pending</span>
|
||||
@endif
|
||||
|
||||
<form method="POST" action="{{ route('admin.data-warga.selesai-validasi',$warga->id) }}">
|
||||
@csrf
|
||||
<button type="submit" class="btn-act btn-kirim">
|
||||
|
|
@ -328,7 +313,6 @@
|
|||
</form>
|
||||
@endif
|
||||
</div>
|
||||
|
||||
<script type="application/json" id="warga-{{ $warga->id }}">{!! json_encode($dp,JSON_UNESCAPED_UNICODE|JSON_UNESCAPED_SLASHES) !!}</script>
|
||||
</td>
|
||||
</tr>
|
||||
|
|
@ -351,7 +335,9 @@
|
|||
</div>
|
||||
|
||||
@if(isset($wargas) && method_exists($wargas,'links') && $wargas->hasPages())
|
||||
<div style="padding:10px 16px;border-top:1px solid #f1f5f9;background:#fafafa;">{{ $wargas->links() }}</div>
|
||||
<div style="padding:10px 16px;border-top:1px solid #f1f5f9;background:#fafafa;">
|
||||
{{ $wargas->links() }}
|
||||
</div>
|
||||
@endif
|
||||
</div>
|
||||
|
||||
|
|
@ -359,19 +345,30 @@
|
|||
<div id="detailBackdrop" class="m-backdrop" style="display:none;" onclick="closeDetailModal()"></div>
|
||||
<div id="detailModal" class="m-wrap" style="display:none;">
|
||||
<div class="m-box">
|
||||
|
||||
<div class="mhero">
|
||||
<div class="mhero-b1"></div><div class="mhero-b2"></div>
|
||||
<div class="mhero-row">
|
||||
<div style="display:flex;align-items:center;gap:12px;min-width:0;">
|
||||
<div style="display:flex;align-items:center;gap:10px;min-width:0;">
|
||||
<div class="m-av"><span id="modal-initial">?</span></div>
|
||||
<div style="min-width:0;"><div class="m-title" id="modal-title">—</div><div class="m-sub" id="modal-subtitle">—</div></div>
|
||||
<div style="min-width:0;">
|
||||
<div class="m-title" id="modal-title">—</div>
|
||||
<div class="m-sub" id="modal-subtitle">—</div>
|
||||
</div>
|
||||
</div>
|
||||
<button class="m-close" onclick="closeDetailModal()"><svg fill="none" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12"/></svg></button>
|
||||
<button class="m-close" onclick="closeDetailModal()">
|
||||
<svg fill="none" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12"/></svg>
|
||||
</button>
|
||||
</div>
|
||||
<div class="m-st-pill">
|
||||
<span style="width:6px;height:6px;border-radius:50%;flex-shrink:0;" id="modal-status-dot"></span>
|
||||
<span id="modal-status-text">—</span>
|
||||
</div>
|
||||
<div class="m-st-pill"><span style="width:6px;height:6px;border-radius:50%;flex-shrink:0;" id="modal-status-dot"></span><span id="modal-status-text">—</span></div>
|
||||
</div>
|
||||
|
||||
<div class="m-body">
|
||||
|
||||
{{-- Identitas --}}
|
||||
<div class="msec">
|
||||
<div class="msec-h"><div class="bar" style="background:#2563eb;"></div><h4>Data Identitas</h4></div>
|
||||
<div class="msec-b">
|
||||
|
|
@ -382,11 +379,12 @@
|
|||
<div><p class="mlbl">Jenis Kelamin</p><p id="d_jk" class="mval">-</p></div>
|
||||
<div><p class="mlbl">Tempat, Tgl Lahir</p><p id="d_lahir" class="mval">-</p></div>
|
||||
<div><p class="mlbl">Usia</p><p id="d_usia" class="mval">-</p></div>
|
||||
<div style="grid-column:span 2;"><p class="mlbl">Status Perkawinan</p><p id="d_kawin" class="mval">-</p></div>
|
||||
<div><p class="mlbl">Status Perkawinan</p><p id="d_kawin" class="mval">-</p></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{-- Tempat Tinggal --}}
|
||||
<div class="msec">
|
||||
<div class="msec-h"><div class="bar" style="background:#10b981;"></div><h4>Tempat Tinggal</h4></div>
|
||||
<div class="msec-b">
|
||||
|
|
@ -395,10 +393,14 @@
|
|||
<div><p class="mlbl">Dusun</p><p id="d_dusun" class="mval">-</p></div>
|
||||
<div><p class="mlbl">RT</p><p id="d_rt" class="mval">-</p></div>
|
||||
<div style="grid-column:span 2;"><p class="mlbl">Aset Kepemilikan</p><p id="d_aset" class="mval">-</p></div>
|
||||
<div><p class="mlbl">Kondisi Rumah</p><p id="d_kondisi_rumah" class="mval">-</p></div>
|
||||
<div><p class="mlbl">Meteran Listrik</p><p id="d_meteran_listrik" class="mval">-</p></div>
|
||||
<div><p class="mlbl">Sumber Air</p><p id="d_sumber_air" class="mval">-</p></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{-- Ekonomi --}}
|
||||
<div class="msec">
|
||||
<div class="msec-h"><div class="bar" style="background:#f59e0b;"></div><h4>Data Ekonomi</h4></div>
|
||||
<div class="msec-b">
|
||||
|
|
@ -411,36 +413,52 @@
|
|||
</div>
|
||||
</div>
|
||||
|
||||
{{-- Prediksi --}}
|
||||
<div class="msec">
|
||||
<div class="msec-h"><div class="bar" style="background:#2563eb;"></div><h4>Prediksi Kelayakan</h4></div>
|
||||
<div class="msec-b">
|
||||
<div id="d_pred_empty" style="display:none;padding:10px 0;text-align:center;font-size:11.5px;color:#9ca3af;">Belum ada data prediksi.</div>
|
||||
<div id="d_pred_empty" style="display:none;padding:8px 0;text-align:center;font-size:11.5px;color:#9ca3af;">Belum ada data prediksi.</div>
|
||||
<div id="d_pred_wrap">
|
||||
<div style="display:flex;align-items:center;gap:14px;margin-bottom:10px;">
|
||||
<div style="font-size:28px;font-weight:900;color:#111827;line-height:1;" id="d_prob">—</div>
|
||||
<div style="flex:1;height:6px;background:#f1f5f9;border-radius:99px;overflow:hidden;"><div id="d_prob_bar" style="height:100%;border-radius:99px;width:0%;transition:width .5s;background:#10b981;"></div></div>
|
||||
<div style="display:flex;align-items:center;gap:12px;margin-bottom:10px;">
|
||||
<div style="font-size:26px;font-weight:900;color:#111827;line-height:1;" id="d_prob">—</div>
|
||||
<div style="flex:1;height:6px;background:#f1f5f9;border-radius:99px;overflow:hidden;">
|
||||
<div id="d_prob_bar" style="height:100%;border-radius:99px;width:0%;transition:width .5s;background:#10b981;"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div><p class="mlbl" style="margin-bottom:5px;">Rekomendasi</p><span id="d_reco_badge" style="display:inline-flex;padding:4px 12px;border-radius:9px;font-size:12px;font-weight:700;background:#f3f4f6;color:#374151;">—</span></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{-- Penjelasan --}}
|
||||
<div class="msec">
|
||||
<div class="msec-h"><div class="bar" style="background:#10b981;"></div><h4>Penjelasan Prediksi</h4></div>
|
||||
<div class="msec-b" style="display:flex;flex-direction:column;gap:10px;">
|
||||
<div><p class="mlbl" style="margin-bottom:4px;">Faktor Pendukung</p><ul id="d_positive" style="list-style:none;padding:0;margin:0;font-size:12px;color:#166534;display:flex;flex-direction:column;gap:2px;"><li>-</li></ul></div>
|
||||
<div><p class="mlbl" style="margin-bottom:4px;">Faktor Pengurang</p><ul id="d_negative" style="list-style:none;padding:0;margin:0;font-size:12px;color:#9f1239;display:flex;flex-direction:column;gap:2px;"><li>-</li></ul></div>
|
||||
<div><p class="mlbl" style="margin-bottom:4px;">Ringkasan</p><p id="d_summary" style="font-size:12px;color:#374151;line-height:1.7;">-</p></div>
|
||||
<div>
|
||||
<p class="mlbl" style="margin-bottom:4px;">Faktor Pendukung</p>
|
||||
<ul id="d_positive" style="list-style:none;padding:0;margin:0;font-size:12px;color:#166534;display:flex;flex-direction:column;gap:2px;"><li>-</li></ul>
|
||||
</div>
|
||||
<div>
|
||||
<p class="mlbl" style="margin-bottom:4px;">Faktor Pengurang</p>
|
||||
<ul id="d_negative" style="list-style:none;padding:0;margin:0;font-size:12px;color:#9f1239;display:flex;flex-direction:column;gap:2px;"><li>-</li></ul>
|
||||
</div>
|
||||
<div>
|
||||
<p class="mlbl" style="margin-bottom:4px;">Ringkasan</p>
|
||||
<p id="d_summary" style="font-size:12px;color:#374151;line-height:1.7;">-</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div style="display:flex;justify-content:space-between;font-size:11px;color:#9ca3af;padding:0 2px;">
|
||||
<div style="display:flex;justify-content:space-between;flex-wrap:wrap;gap:4px;font-size:11px;color:#9ca3af;padding:0 2px;">
|
||||
<span>Input oleh: <strong id="d_input_oleh" style="color:#374151;">-</strong></span>
|
||||
<span>Didaftarkan: <strong id="d_created" style="color:#374151;">-</strong></span>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<div class="m-footer"><button onclick="closeDetailModal()" class="btn-close-m">Tutup</button></div>
|
||||
<div class="m-footer">
|
||||
<button onclick="closeDetailModal()" class="btn-close-m">Tutup</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
|
@ -451,34 +469,40 @@ function setList(id,items,empty='-'){
|
|||
if(!items||!items.length){e.innerHTML=`<li>${empty}</li>`;return;}
|
||||
e.innerHTML=items.map(i=>`<li style="display:flex;gap:5px;"><span>•</span><span>${i}</span></li>`).join('');
|
||||
}
|
||||
|
||||
function openDetailModal(id){
|
||||
const el=document.getElementById('warga-'+id);if(!el)return;
|
||||
const d=JSON.parse(el.textContent);
|
||||
|
||||
set('modal-initial',(d.nama_lengkap||'?').charAt(0).toUpperCase());
|
||||
set('modal-title',d.nama_lengkap||'-');
|
||||
set('modal-subtitle',(d.pekerjaan?d.pekerjaan+' · ':'')+(d.dusun||'-'));
|
||||
set('modal-subtitle',(d.pekerjaan?d.pekerjaan+' · ':'')+( d.dusun||'-'));
|
||||
|
||||
const dot=document.getElementById('modal-status-dot');
|
||||
const txt=document.getElementById('modal-status-text');
|
||||
const st=d.status_verifikasi||'';
|
||||
const dotStyles={pending:'background:#fcd34d;',disetujui:'background:#6ee7b7;',default:'background:#fca5a5;'};
|
||||
if(dot) dot.style.cssText='width:6px;height:6px;border-radius:50%;flex-shrink:0;'+(dotStyles[st]||dotStyles.default);
|
||||
const dotC={pending:'#fcd34d',disetujui:'#6ee7b7'};
|
||||
if(dot) dot.style.cssText=`width:6px;height:6px;border-radius:50%;flex-shrink:0;background:${dotC[st]||'#fca5a5'};`;
|
||||
if(txt) txt.textContent=st==='pending'?'Menunggu Hasil Filterisasi':st==='disetujui'?'Diterima':'Tidak Diterima';
|
||||
|
||||
set('d_nik',d.nik||'-');set('d_no_kk',d.no_kk||'-');set('d_nama',d.nama_lengkap||'-');
|
||||
set('d_jk',d.jenis_kelamin||'-');set('d_lahir',(d.tempat_lahir||'-')+', '+(d.tanggal_lahir||'-'));
|
||||
set('d_nik',d.nik||'-');set('d_no_kk',d.no_kk||'-');
|
||||
set('d_nama',d.nama_lengkap||'-');set('d_jk',d.jenis_kelamin||'-');
|
||||
set('d_lahir',(d.tempat_lahir||'-')+', '+(d.tanggal_lahir||'-'));
|
||||
set('d_usia',d.usia?d.usia+' tahun':'-');set('d_kawin',d.status_perkawinan||'-');
|
||||
set('d_alamat', d.alamat || '-');
|
||||
set('d_dusun', d.dusun || '-');
|
||||
set('d_rt','RT '+String(d.rt||'-').padStart(3,'0'));set('d_aset',d.aset_kepemilikan||'-');
|
||||
set('d_alamat',d.alamat||'-');set('d_dusun',d.dusun||'-');
|
||||
set('d_rt','RT '+String(d.rt||'-').padStart(3,'0'));
|
||||
set('d_aset',d.aset_kepemilikan||'-');
|
||||
set('d_kondisi_rumah',d.kondisi_rumah||'-');
|
||||
set('d_meteran_listrik',d.meteran_listrik||'-');
|
||||
set('d_sumber_air',d.sumber_air||'-');
|
||||
set('d_pekerjaan',d.pekerjaan||'-');
|
||||
set('d_penghasilan','Rp '+Number(d.penghasilan||0).toLocaleString('id-ID'));
|
||||
set('d_tanggungan',d.jumlah_tanggungan!=null?d.jumlah_tanggungan+' orang':'-');
|
||||
set('d_bantuan',d.bantuan_lain||'-');
|
||||
|
||||
const prob=d.probability;
|
||||
const emEl=document.getElementById('d_pred_empty');const wrEl=document.getElementById('d_pred_wrap');
|
||||
const emEl=document.getElementById('d_pred_empty');
|
||||
const wrEl=document.getElementById('d_pred_wrap');
|
||||
if(prob===null||prob===undefined){
|
||||
if(emEl)emEl.style.display='block';
|
||||
if(wrEl)wrEl.style.display='none';
|
||||
|
|
@ -508,12 +532,14 @@ function openDetailModal(id){
|
|||
document.getElementById('detailModal').style.display='flex';
|
||||
document.body.style.overflow='hidden';
|
||||
}
|
||||
|
||||
function closeDetailModal(){
|
||||
document.getElementById('detailBackdrop').style.display='none';
|
||||
document.getElementById('detailModal').style.display='none';
|
||||
document.body.style.overflow='';
|
||||
}
|
||||
document.addEventListener('keydown',e=>{if(e.key==='Escape')closeDetailModal();});
|
||||
|
||||
document.addEventListener('keydown',e=>{ if(e.key==='Escape') closeDetailModal(); });
|
||||
</script>
|
||||
|
||||
</x-app-layout>
|
||||
|
|
@ -1,41 +1,104 @@
|
|||
<x-app-layout>
|
||||
|
||||
<x-slot name="header">
|
||||
<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 tertinggi per dusun.
|
||||
</p>
|
||||
<div style="display:flex;align-items:center;justify-content:space-between;gap:12px;flex-wrap:wrap;">
|
||||
<div>
|
||||
<h2 style="font-size:16px;font-weight:800;color:#0f172a;letter-spacing:-.02em;">Filterisasi</h2>
|
||||
<p style="font-size:11px;color:#94a3b8;margin-top:2px;">Penetapan penerima BLT-DD berdasarkan probabilitas tertinggi per dusun</p>
|
||||
</div>
|
||||
</div>
|
||||
</x-slot>
|
||||
|
||||
<div class="space-y-4">
|
||||
<style>
|
||||
.fi-card{background:#fff;border-radius:16px;border:1.5px solid #f1f5f9;box-shadow:0 1px 4px rgba(0,0,0,.04);overflow:hidden;}
|
||||
.fl-box{display:flex;align-items:center;gap:9px;border-radius:12px;padding:10px 14px;font-size:12px;font-weight:500;margin-bottom:10px;}
|
||||
.fl-box svg{width:14px;height:14px;flex-shrink:0;}
|
||||
|
||||
@if(session('success'))
|
||||
<div class="rounded-xl border border-green-200 bg-green-50 px-4 py-3 text-sm text-green-700">
|
||||
{{ session('success') }}
|
||||
</div>
|
||||
@endif
|
||||
/* form controls */
|
||||
.fi-label{display:block;font-size:10.5px;font-weight:600;color:#64748b;margin-bottom:5px;}
|
||||
.fi-select,.fi-input{width:100%;padding:9px 12px;font-size:13px;color:#111827;background:#f9fafb;border:1.5px solid #e5e7eb;border-radius:11px;outline:none;font-family:inherit;transition:border-color .15s;}
|
||||
.fi-select:focus,.fi-input:focus{border-color:#3b82f6;background:#fff;}
|
||||
|
||||
@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') }}
|
||||
</div>
|
||||
@endif
|
||||
/* buttons */
|
||||
.btn-fi{display:inline-flex;align-items:center;gap:6px;padding:9px 18px;font-size:12.5px;font-weight:700;border:none;border-radius:11px;cursor:pointer;white-space:nowrap;font-family:inherit;transition:filter .1s;}
|
||||
.btn-fi:hover{filter:brightness(.93);}
|
||||
.btn-tampil{background:#2563eb;color:#fff;box-shadow:0 2px 8px rgba(37,99,235,.2);}
|
||||
.btn-reset-fi{background:#f1f5f9;color:#475569;}
|
||||
.btn-tetapkan{background:#059669;color:#fff;box-shadow:0 2px 8px rgba(5,150,105,.2);}
|
||||
|
||||
<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</h3>
|
||||
</div>
|
||||
/* table */
|
||||
.tbl-wrap{overflow-x:auto;-webkit-overflow-scrolling:touch;}
|
||||
table{width:100%;border-collapse:collapse;min-width:580px;}
|
||||
thead tr{background:#f8fafc;border-bottom:1.5px solid #f1f5f9;}
|
||||
thead th{padding:9px 12px;text-align:left;font-size:9.5px;font-weight:700;color:#94a3b8;text-transform:uppercase;letter-spacing:.07em;white-space:nowrap;}
|
||||
tbody tr{border-bottom:1px solid #f8fafc;transition:background .1s;}
|
||||
tbody tr:hover{background:#f0f7ff;}
|
||||
tbody tr:last-child{border-bottom:none;}
|
||||
tbody td{padding:10px 12px;vertical-align:middle;font-size:12.5px;}
|
||||
|
||||
<form method="GET" action="{{ route('admin.filterisasi') }}" class="grid grid-cols-1 md:grid-cols-4 gap-3">
|
||||
.badge{display:inline-flex;align-items:center;gap:4px;padding:3px 9px;border-radius:20px;font-size:10.5px;font-weight:700;white-space:nowrap;}
|
||||
.dot{width:5px;height:5px;border-radius:50%;flex-shrink:0;}
|
||||
|
||||
.prob-wrap{display:flex;align-items:center;gap:6px;}
|
||||
.prob-bg{flex:1;height:5px;background:#f1f5f9;border-radius:99px;overflow:hidden;min-width:50px;}
|
||||
.prob-bar{height:100%;border-radius:99px;}
|
||||
|
||||
/* ranking badge */
|
||||
.rank-badge{width:26px;height:26px;border-radius:8px;display:flex;align-items:center;justify-content:center;font-size:11px;font-weight:800;}
|
||||
|
||||
/* note box */
|
||||
.note-box{background:#fffbeb;border:1.5px solid #fde68a;border-radius:14px;padding:14px 16px;font-size:12px;color:#92400e;line-height:1.8;}
|
||||
.note-box b{font-weight:800;}
|
||||
|
||||
/* filter form grid */
|
||||
.fi-grid{display:grid;grid-template-columns:1fr 1fr auto;gap:10px;align-items:end;}
|
||||
.fi-actions{display:flex;gap:8px;flex-wrap:wrap;align-items:center;}
|
||||
|
||||
/* mobile card */
|
||||
.mob-fi{display:none;flex-direction:column;gap:8px;padding:12px 14px;}
|
||||
.mob-fi-row{background:#f8fafc;border:1.5px solid #f1f5f9;border-radius:12px;padding:11px 13px;}
|
||||
|
||||
@media(max-width:768px){
|
||||
.fi-grid{grid-template-columns:1fr 1fr;gap:8px;}
|
||||
.fi-grid > *:last-child{grid-column:span 2;}
|
||||
}
|
||||
@media(max-width:560px){
|
||||
.fi-grid{grid-template-columns:1fr;}
|
||||
.fi-grid > *:last-child{grid-column:span 1;}
|
||||
.tbl-wrap table{display:none;}
|
||||
.mob-fi{display:flex;}
|
||||
}
|
||||
</style>
|
||||
|
||||
{{-- FLASH --}}
|
||||
@if(session('success'))
|
||||
<div class="fl-box" 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="fl-box" 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
|
||||
|
||||
{{-- FILTER FORM --}}
|
||||
<div class="fi-card" style="padding:14px 16px;margin-bottom:12px;">
|
||||
<div style="display:flex;align-items:center;gap:7px;margin-bottom:12px;">
|
||||
<div style="width:3px;height:14px;background:#2563eb;border-radius:4px;"></div>
|
||||
<h3 style="font-size:12px;font-weight:700;color:#374151;">Pilih Dusun & Kuota</h3>
|
||||
</div>
|
||||
|
||||
<form method="GET" action="{{ route('admin.filterisasi') }}">
|
||||
<div class="fi-grid">
|
||||
<div>
|
||||
<label class="text-xs text-gray-500">Dusun</label>
|
||||
<select name="dusun_id" class="mt-1 w-full border border-gray-200 rounded-xl bg-gray-50 px-3 py-2 text-sm">
|
||||
<label class="fi-label">Dusun</label>
|
||||
<select name="dusun_id" class="fi-select">
|
||||
<option value="0">-- pilih dusun --</option>
|
||||
@foreach($dusuns as $d)
|
||||
<option value="{{ $d->id }}" {{ (int)($dusunId ?? 0) === $d->id ? 'selected' : '' }}>
|
||||
<option value="{{ $d->id }}" {{ (int)($dusunId??0)===$d->id?'selected':'' }}>
|
||||
{{ $d->nama_dusun }}
|
||||
</option>
|
||||
@endforeach
|
||||
|
|
@ -43,137 +106,237 @@
|
|||
</div>
|
||||
|
||||
<div>
|
||||
<label class="text-xs text-gray-500">Kuota per dusun</label>
|
||||
<input type="number" name="kuota" min="1" max="100" value="{{ (int)($kuota ?? 7) }}"
|
||||
class="mt-1 w-full border border-gray-200 rounded-xl bg-gray-50 px-3 py-2 text-sm" />
|
||||
<label class="fi-label">Kuota per Dusun</label>
|
||||
<input type="number" name="kuota" min="1" max="100"
|
||||
value="{{ (int)($kuota??7) }}" class="fi-input">
|
||||
</div>
|
||||
|
||||
<div class="flex items-end gap-2 md:col-span-2">
|
||||
<button type="submit"
|
||||
class="px-5 py-2 text-sm font-semibold bg-blue-600 text-white rounded-xl hover:bg-blue-700 transition">
|
||||
<div class="fi-actions">
|
||||
<button type="submit" class="btn-fi btn-tampil">
|
||||
<svg width="13" height="13" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M3 4a1 1 0 011-1h16a1 1 0 011 1v2a1 1 0 01-.293.707L13 13.414V19a1 1 0 01-.553.894l-4 2A1 1 0 017 21v-7.586L3.293 6.707A1 1 0 013 6V4z"/></svg>
|
||||
Tampilkan
|
||||
</button>
|
||||
|
||||
@if(!empty($dusunId))
|
||||
<form method="POST" action="{{ route('admin.filterisasi.reset') }}">
|
||||
<form method="POST" action="{{ route('admin.filterisasi.reset') }}" style="display:contents;">
|
||||
@csrf
|
||||
<input type="hidden" name="dusun_id" value="{{ $dusunId }}">
|
||||
<button type="submit"
|
||||
onclick="return confirm('Reset hasil filterisasi dusun ini?')"
|
||||
class="px-4 py-2 text-sm font-semibold bg-gray-100 text-gray-700 rounded-xl hover:bg-gray-200 transition">
|
||||
<button type="submit" class="btn-fi btn-reset-fi"
|
||||
onclick="return confirm('Reset hasil filterisasi dusun ini?')">
|
||||
<svg width="13" height="13" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 4v5h.582m15.356 2A8.001 8.001 0 004.582 9m0 0H9m11 11v-5h-.581m0 0a8.003 8.003 0 01-15.357-2m15.357 2H15"/></svg>
|
||||
Reset
|
||||
</button>
|
||||
</form>
|
||||
|
||||
<form method="POST" action="{{ route('admin.filterisasi.tetapkan') }}" class="ml-auto">
|
||||
<form method="POST" action="{{ route('admin.filterisasi.tetapkan') }}" style="display:contents;">
|
||||
@csrf
|
||||
<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 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
|
||||
<input type="hidden" name="kuota" value="{{ (int)($kuota??7) }}">
|
||||
<button type="submit" class="btn-fi btn-tetapkan"
|
||||
onclick="return confirm('Tetapkan {{ (int)($kuota??7) }} orang dengan probabilitas tertinggi untuk dusun ini?')">
|
||||
<svg width="13" height="13" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z"/></svg>
|
||||
Tetapkan {{ (int)($kuota??7) }} Teratas
|
||||
</button>
|
||||
</form>
|
||||
@endif
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
{{-- TABLE CARD --}}
|
||||
<div class="fi-card" style="margin-bottom:12px;">
|
||||
<div style="padding:10px 16px;border-bottom:1px solid #f1f5f9;background:#fafafa;display:flex;align-items:center;gap:7px;">
|
||||
<div style="width:3px;height:15px;background:#2563eb;border-radius:4px;"></div>
|
||||
<span style="font-size:12.5px;font-weight:700;color:#374151;">Kandidat Sedang Divalidasi / Sudah Diproses</span>
|
||||
</div>
|
||||
|
||||
<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 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 Sedang Divalidasi / Sudah Diproses</span>
|
||||
</div>
|
||||
</div>
|
||||
{{-- DESKTOP TABLE --}}
|
||||
<div class="tbl-wrap">
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th style="width:52px;">Rank</th>
|
||||
<th>Nama</th>
|
||||
<th>NIK</th>
|
||||
<th>Dusun</th>
|
||||
<th style="min-width:130px;">Probabilitas</th>
|
||||
<th>Status</th>
|
||||
<th>Final</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@forelse(($candidates??[]) as $index => $w)
|
||||
@php
|
||||
$prob = $w->prob ?? optional($w->prediksiKelayakan)->probability;
|
||||
$probPct = $prob !== null ? ($prob<=1 ? $prob*100 : $prob) : null;
|
||||
$sc = $probPct !== null ? ($probPct>=70?'#10b981':($probPct>=40?'#f59e0b':'#f43f5e')) : '#e5e7eb';
|
||||
$picked = isset($pickedIds) ? $pickedIds->contains($w->id) : false;
|
||||
$rank = (($candidates->currentPage()-1)*$candidates->perPage())+$index+1;
|
||||
@endphp
|
||||
<tr style="{{ $picked ? 'background:#f0fdf4;' : '' }}">
|
||||
|
||||
<div class="overflow-x-auto">
|
||||
<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">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>
|
||||
{{-- Rank --}}
|
||||
<td>
|
||||
@php
|
||||
$rk = ['linear-gradient(135deg,#f59e0b,#d97706)','linear-gradient(135deg,#94a3b8,#64748b)','linear-gradient(135deg,#f97316,#ea580c)'];
|
||||
$rbg = $rank<=3 ? $rk[$rank-1] : '#f1f5f9';
|
||||
$rtc = $rank<=3 ? '#fff' : '#64748b';
|
||||
@endphp
|
||||
<div class="rank-badge" style="background:{{ $rbg }};color:{{ $rtc }};">{{ $rank }}</div>
|
||||
</td>
|
||||
|
||||
{{-- Nama --}}
|
||||
<td>
|
||||
<div style="display:flex;align-items:center;gap:8px;">
|
||||
<div style="width:28px;height:28px;border-radius:8px;background:linear-gradient(135deg,#3b82f6,#2563eb);display:flex;align-items:center;justify-content:center;font-size:11px;font-weight:800;color:#fff;flex-shrink:0;">
|
||||
{{ strtoupper(substr($w->nama_lengkap,0,1)) }}
|
||||
</div>
|
||||
<span style="font-size:12px;font-weight:600;color:#0f172a;">{{ $w->nama_lengkap }}</span>
|
||||
</div>
|
||||
</td>
|
||||
|
||||
{{-- NIK --}}
|
||||
<td>
|
||||
<span style="font-family:monospace;font-size:10.5px;color:#6b7280;background:#f3f4f6;padding:2px 7px;border-radius:6px;">{{ $w->nik }}</span>
|
||||
</td>
|
||||
|
||||
{{-- Dusun --}}
|
||||
<td style="font-size:12px;color:#374151;">
|
||||
{{ optional(optional($w->rt)->dusun)->nama_dusun ?? '-' }}
|
||||
</td>
|
||||
|
||||
{{-- Probabilitas --}}
|
||||
<td>
|
||||
@if($probPct !== null)
|
||||
<div class="prob-wrap">
|
||||
<div class="prob-bg">
|
||||
<div class="prob-bar" style="width:{{ min($probPct,100) }}%;background:{{ $sc }};"></div>
|
||||
</div>
|
||||
<span style="font-size:11.5px;font-weight:700;color:{{ $sc }};white-space:nowrap;">{{ number_format((float)$probPct,1) }}%</span>
|
||||
</div>
|
||||
@else
|
||||
<span style="color:#d1d5db;">—</span>
|
||||
@endif
|
||||
</td>
|
||||
|
||||
{{-- Status verifikasi --}}
|
||||
<td>
|
||||
@php
|
||||
$sv=['pending'=>['#fffbeb','#b45309','#fde68a','Pending'],'disetujui'=>['#f0fdf4','#166534','#bbf7d0','Disetujui'],'ditolak'=>['#fff1f2','#9f1239','#fecdd3','Ditolak']];
|
||||
$sv=$sv[$w->status_verifikasi??'']??['#f3f4f6','#6b7280','#e5e7eb',ucfirst($w->status_verifikasi??'-')];
|
||||
@endphp
|
||||
<span class="badge" style="background:{{ $sv[0] }};color:{{ $sv[1] }};border:1px solid {{ $sv[2] }};">
|
||||
<span class="dot" style="background:{{ $sv[1] }};"></span>{{ $sv[3] }}
|
||||
</span>
|
||||
</td>
|
||||
|
||||
{{-- Final (terpilih) --}}
|
||||
<td>
|
||||
@if($picked)
|
||||
<span class="badge" style="background:#f0fdf4;color:#166534;border:1px solid #bbf7d0;">
|
||||
<span class="dot" style="background:#10b981;"></span>Terpilih
|
||||
</span>
|
||||
@else
|
||||
<span class="badge" style="background:#f3f4f6;color:#6b7280;border:1px solid #e5e7eb;">
|
||||
<span class="dot" style="background:#9ca3af;"></span>Belum
|
||||
</span>
|
||||
@endif
|
||||
</td>
|
||||
</tr>
|
||||
</thead>
|
||||
@empty
|
||||
<tr><td colspan="7">
|
||||
<div style="padding:48px 16px;text-align:center;">
|
||||
<div style="width:44px;height:44px;background:#f3f4f6;border-radius:50%;display:flex;align-items:center;justify-content:center;margin:0 auto 10px;">
|
||||
<svg width="22" height="22" fill="none" stroke="#d1d5db" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" d="M3 4a1 1 0 011-1h16a1 1 0 011 1v2a1 1 0 01-.293.707L13 13.414V19a1 1 0 01-.553.894l-4 2A1 1 0 017 21v-7.586L3.293 6.707A1 1 0 013 6V4z"/></svg>
|
||||
</div>
|
||||
<p style="font-size:13px;font-weight:600;color:#6b7280;">Pilih dusun untuk menampilkan kandidat</p>
|
||||
</div>
|
||||
</td></tr>
|
||||
@endforelse
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<tbody class="divide-y divide-gray-50">
|
||||
@forelse(($candidates ?? []) as $index => $w)
|
||||
@php
|
||||
$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 text-sm font-semibold text-gray-700">
|
||||
{{ (($candidates->currentPage() - 1) * $candidates->perPage()) + $index + 1 }}
|
||||
</td>
|
||||
{{-- MOBILE CARD VIEW --}}
|
||||
<div class="mob-fi">
|
||||
@forelse(($candidates??[]) as $index => $w)
|
||||
@php
|
||||
$prob = $w->prob ?? optional($w->prediksiKelayakan)->probability;
|
||||
$probPct = $prob !== null ? ($prob<=1 ? $prob*100 : $prob) : null;
|
||||
$sc = $probPct!==null ? ($probPct>=70?'#10b981':($probPct>=40?'#f59e0b':'#f43f5e')) : '#e5e7eb';
|
||||
$picked = isset($pickedIds) ? $pickedIds->contains($w->id) : false;
|
||||
$rank = (($candidates->currentPage()-1)*$candidates->perPage())+$index+1;
|
||||
$sv=['pending'=>['#fffbeb','#b45309','Pending'],'disetujui'=>['#f0fdf4','#166534','Disetujui'],'ditolak'=>['#fff1f2','#9f1239','Ditolak']];
|
||||
$sv=$sv[$w->status_verifikasi??'']??['#f3f4f6','#6b7280',ucfirst($w->status_verifikasi??'-')];
|
||||
@endphp
|
||||
<div class="mob-fi-row" style="{{ $picked ? 'background:#f0fdf4;border-color:#bbf7d0;' : '' }}">
|
||||
<div style="display:flex;align-items:center;gap:10px;">
|
||||
{{-- rank --}}
|
||||
@php
|
||||
$rk = ['linear-gradient(135deg,#f59e0b,#d97706)','linear-gradient(135deg,#94a3b8,#64748b)','linear-gradient(135deg,#f97316,#ea580c)'];
|
||||
$rbg = $rank<=3 ? $rk[$rank-1] : '#f1f5f9';
|
||||
$rtc = $rank<=3 ? '#fff' : '#64748b';
|
||||
@endphp
|
||||
<div class="rank-badge" style="background:{{ $rbg }};color:{{ $rtc }};flex-shrink:0;">{{ $rank }}</div>
|
||||
|
||||
<td class="px-4 py-3 font-semibold text-gray-800">{{ $w->nama_lengkap }}</td>
|
||||
{{-- avatar + nama --}}
|
||||
<div style="flex:1;min-width:0;">
|
||||
<div style="font-size:13px;font-weight:700;color:#0f172a;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;">{{ $w->nama_lengkap }}</div>
|
||||
<div style="font-size:10.5px;color:#64748b;margin-top:1px;font-family:monospace;">{{ $w->nik }}</div>
|
||||
</div>
|
||||
|
||||
<td class="px-4 py-3 text-gray-600">{{ $w->nik }}</td>
|
||||
{{-- final status --}}
|
||||
@if($picked)
|
||||
<span class="badge" style="background:#f0fdf4;color:#166534;border:1px solid #bbf7d0;flex-shrink:0;">✓ Terpilih</span>
|
||||
@endif
|
||||
</div>
|
||||
|
||||
<td class="px-4 py-3 text-gray-600">{{ optional(optional($w->rt)->dusun)->nama_dusun ?? '-' }}</td>
|
||||
<div style="display:flex;align-items:center;gap:8px;margin-top:10px;flex-wrap:wrap;">
|
||||
{{-- dusun --}}
|
||||
<span style="font-size:11px;color:#64748b;background:#f1f5f9;padding:2px 8px;border-radius:6px;">
|
||||
{{ optional(optional($w->rt)->dusun)->nama_dusun ?? '-' }}
|
||||
</span>
|
||||
|
||||
<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>
|
||||
{{-- status --}}
|
||||
<span class="badge" style="background:{{ $sv[0] }};color:{{ $sv[1] }};border:1px solid {{ $sv[0] }};">
|
||||
<span class="dot" style="background:{{ $sv[1] }};"></span>{{ $sv[2] }}
|
||||
</span>
|
||||
|
||||
<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>
|
||||
@else
|
||||
<span class="px-2 py-1 rounded-full text-xs bg-gray-100 text-gray-600">Belum</span>
|
||||
@endif
|
||||
</td>
|
||||
</tr>
|
||||
@empty
|
||||
<tr>
|
||||
<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
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
@if(isset($candidates) && method_exists($candidates, 'links') && $candidates->hasPages())
|
||||
<div class="px-5 py-3 border-t border-gray-100 bg-gray-50">
|
||||
{{ $candidates->appends(request()->query())->links() }}
|
||||
{{-- probabilitas --}}
|
||||
@if($probPct !== null)
|
||||
<div style="display:flex;align-items:center;gap:6px;flex:1;min-width:100px;">
|
||||
<div style="flex:1;height:5px;background:#e2e8f0;border-radius:99px;overflow:hidden;">
|
||||
<div style="height:100%;border-radius:99px;background:{{ $sc }};width:{{ min($probPct,100) }}%;"></div>
|
||||
</div>
|
||||
<span style="font-size:11px;font-weight:700;color:{{ $sc }};white-space:nowrap;">{{ number_format((float)$probPct,1) }}%</span>
|
||||
</div>
|
||||
@endif
|
||||
</div>
|
||||
</div>
|
||||
@endif
|
||||
@empty
|
||||
<div style="padding:32px 16px;text-align:center;">
|
||||
<p style="font-size:13px;font-weight:600;color:#6b7280;">Pilih dusun untuk menampilkan kandidat</p>
|
||||
</div>
|
||||
@endforelse
|
||||
</div>
|
||||
|
||||
<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>
|
||||
{{-- PAGINATION --}}
|
||||
@if(isset($candidates) && method_exists($candidates,'links') && $candidates->hasPages())
|
||||
<div style="padding:10px 16px;border-top:1px solid #f1f5f9;background:#fafafa;">
|
||||
{{ $candidates->appends(request()->query())->links() }}
|
||||
</div>
|
||||
@endif
|
||||
</div>
|
||||
|
||||
{{-- NOTE --}}
|
||||
<div class="note-box">
|
||||
<b>Catatan:</b> Kandidat yang tampil adalah data warga 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>
|
||||
|
||||
</x-app-layout>
|
||||
|
|
@ -1,90 +1,116 @@
|
|||
<x-app-layout>
|
||||
|
||||
<style>
|
||||
.lp-wrap{max-width:1400px;margin:0 auto;padding:24px 20px 40px;}
|
||||
.lp-card{background:#fff;border-radius:20px;border:1.5px solid #f1f5f9;box-shadow:0 2px 8px rgba(0,0,0,.05);overflow:hidden;margin-bottom:14px;}
|
||||
.sum-grid{display:grid;grid-template-columns:repeat(4,1fr);gap:12px;margin-bottom:14px;}
|
||||
@media(max-width:860px){.sum-grid{grid-template-columns:1fr 1fr;}}
|
||||
@media(max-width:560px){.sum-grid{grid-template-columns:1fr;}}
|
||||
.sum-card{background:#fff;border-radius:18px;border:1.5px solid #f1f5f9;box-shadow:0 1px 6px rgba(0,0,0,.04);padding:16px 18px;}
|
||||
.sum-icon{width:36px;height:36px;border-radius:11px;display:flex;align-items:center;justify-content:center;margin-bottom:10px;}
|
||||
.sum-icon svg{width:18px;height:18px;}
|
||||
.sum-val{font-size:24px;font-weight:900;color:#0f172a;line-height:1;font-family:'Georgia',serif;letter-spacing:-1px;}
|
||||
.sum-val.sm{font-size:16px;letter-spacing:-.5px;line-height:1.3;}
|
||||
.sum-lbl{font-size:10.5px;color:#94a3b8;font-weight:600;margin-top:3px;text-transform:uppercase;letter-spacing:.04em;}
|
||||
.sec-hd{padding:12px 18px;border-bottom:1.5px solid #f1f5f9;background:#fafbfc;display:flex;align-items:center;justify-content:space-between;gap:10px;}
|
||||
.sec-hd-l{display:flex;align-items:center;gap:8px;}
|
||||
.sec-bar{width:3px;height:16px;background:linear-gradient(180deg,#3b82f6,#2563eb);border-radius:4px;}
|
||||
.sec-title{font-size:13px;font-weight:800;color:#1e293b;}
|
||||
.sec-count{font-size:11px;color:#94a3b8;background:#f1f5f9;padding:2px 8px;border-radius:20px;font-weight:600;}
|
||||
.exp-row{display:flex;flex-wrap:wrap;gap:10px;align-items:center;padding:16px 18px;}
|
||||
.exp-btn{display:inline-flex;align-items:center;gap:8px;padding:11px 20px;border-radius:13px;font-size:13px;font-weight:700;text-decoration:none;border:none;cursor:pointer;transition:filter .15s,transform .12s,box-shadow .15s;line-height:1;white-space:nowrap;letter-spacing:-.01em;}
|
||||
.exp-btn svg{width:15px;height:15px;flex-shrink:0;}
|
||||
.exp-btn:hover{filter:brightness(.93);transform:translateY(-1px);}
|
||||
.btn-pdf{background:linear-gradient(135deg,#ef4444,#dc2626);color:#fff;box-shadow:0 4px 14px rgba(220,38,38,.35);}
|
||||
.btn-excel{background:linear-gradient(135deg,#22c55e,#16a34a);color:#fff;box-shadow:0 4px 14px rgba(22,163,74,.35);}
|
||||
.btn-send{background:linear-gradient(135deg,#3b82f6,#2563eb);color:#fff;box-shadow:0 4px 14px rgba(37,99,235,.35);}
|
||||
.exp-meta{font-size:11px;color:#94a3b8;padding-left:4px;}
|
||||
.pub-wrap{padding:16px 18px;}
|
||||
.pub-form{display:grid;grid-template-columns:2fr 2fr auto;gap:10px;align-items:center;}
|
||||
@media(max-width:860px){.pub-form{grid-template-columns:1fr;}}
|
||||
.pub-input{padding:11px 14px;border:1.5px solid #e2e8f0;border-radius:12px;font-size:13px;outline:none;background:#fff;color:#0f172a;}
|
||||
.pub-input:focus{border-color:#93c5fd;box-shadow:0 0 0 3px rgba(37,99,235,.08);}
|
||||
.pub-list{margin-top:16px;display:flex;flex-direction:column;gap:10px;}
|
||||
.pub-item{display:flex;justify-content:space-between;align-items:center;gap:12px;padding:12px 14px;border:1.5px solid #f1f5f9;border-radius:14px;background:#fafbfc;}
|
||||
@media(max-width:760px){.pub-item{flex-direction:column;align-items:flex-start;}}
|
||||
.pub-title{font-size:13px;font-weight:700;color:#0f172a;}
|
||||
.pub-meta{font-size:11px;color:#94a3b8;margin-top:2px;}
|
||||
.pub-actions{display:flex;gap:8px;align-items:center;flex-wrap:wrap;}
|
||||
.btn-gray{padding:9px 14px;border:none;border-radius:12px;background:#e5e7eb;color:#374151;font-size:12px;font-weight:700;cursor:pointer;}
|
||||
.lp-tbl-wrap{overflow-x:auto;}
|
||||
table.lp-tbl{width:100%;border-collapse:collapse;}
|
||||
table.lp-tbl thead tr{background:#f8fafc;border-bottom:1.5px solid #f1f5f9;}
|
||||
table.lp-tbl thead th{padding:10px 12px;text-align:left;font-size:9.5px;font-weight:800;color:#94a3b8;text-transform:uppercase;letter-spacing:.08em;white-space:nowrap;}
|
||||
table.lp-tbl tbody tr{border-bottom:1px solid #f8fafc;transition:background .1s;}
|
||||
table.lp-tbl tbody tr:hover{background:#f0f7ff;}
|
||||
table.lp-tbl tbody tr:last-child{border-bottom:none;}
|
||||
table.lp-tbl tbody td{padding:10px 12px;vertical-align:middle;font-size:12.5px;color:#374151;}
|
||||
.av{width:32px;height:32px;border-radius:10px;background:linear-gradient(135deg,#3b82f6,#2563eb);display:flex;align-items:center;justify-content:center;font-size:12px;font-weight:800;color:#fff;flex-shrink:0;}
|
||||
.name-cell{display:flex;align-items:center;gap:9px;}
|
||||
.name-txt{font-size:12.5px;font-weight:700;color:#0f172a;white-space:nowrap;}
|
||||
.nik-pill{font-family:'Courier New',monospace;font-size:10.5px;color:#64748b;background:#f1f5f9;padding:3px 7px;border-radius:6px;letter-spacing:.03em;}
|
||||
.rt-badge{display:inline-flex;padding:3px 8px;border-radius:7px;background:#eff6ff;color:#1d4ed8;font-size:10.5px;font-weight:800;border:1px solid #bfdbfe;}
|
||||
.bantuan-ya{display:inline-flex;padding:2px 9px;border-radius:20px;background:#fffbeb;color:#b45309;font-size:10.5px;font-weight:700;border:1px solid #fde68a;}
|
||||
.bantuan-tdk{display:inline-flex;padding:2px 9px;border-radius:20px;background:#f8fafc;color:#94a3b8;font-size:10.5px;font-weight:600;}
|
||||
.prob-cell{display:flex;align-items:center;gap:7px;}
|
||||
.prob-bg{width:42px;height:5px;background:#f1f5f9;border-radius:99px;overflow:hidden;flex-shrink:0;}
|
||||
.prob-fill{height:100%;border-radius:99px;}
|
||||
.prob-txt{font-size:11.5px;font-weight:800;white-space:nowrap;}
|
||||
.st-pill{display:inline-flex;align-items:center;gap:5px;padding:4px 10px;border-radius:20px;font-size:10.5px;font-weight:700;}
|
||||
.st-dot{width:5px;height:5px;border-radius:50%;}
|
||||
.empty-state{padding:56px 16px;text-align:center;}
|
||||
.empty-icon{width:48px;height:48px;background:#f1f5f9;border-radius:50%;display:flex;align-items:center;justify-content:center;margin:0 auto 12px;}
|
||||
.empty-icon svg{width:22px;height:22px;}
|
||||
.empty-txt{font-size:13px;font-weight:600;color:#94a3b8;}
|
||||
.tbl-foot{padding:10px 18px;border-top:1.5px solid #f1f5f9;background:#fafbfc;text-align:center;}
|
||||
.credit{font-size:11px;color:#cbd5e1;font-weight:500;letter-spacing:.03em;}
|
||||
.flash-ok{display:flex;align-items:center;gap:8px;background:#f0fdf4;border:1.5px solid #bbf7d0;color:#166534;padding:12px 16px;border-radius:13px;font-size:12.5px;font-weight:600;margin-bottom:14px;}
|
||||
.flash-ok svg{width:16px;height:16px;flex-shrink:0;}
|
||||
</style>
|
||||
<x-slot name="header">
|
||||
<div style="display:flex;align-items:center;justify-content:space-between;gap:12px;flex-wrap:wrap;">
|
||||
<div>
|
||||
<h2 style="font-size:16px;font-weight:800;color:#0f172a;letter-spacing:-.02em;">Laporan Penerima BLT-DD</h2>
|
||||
<p style="font-size:11px;color:#94a3b8;margin-top:2px;">Kelurahan Ngerong — warga yang telah ditetapkan sebagai penerima final</p>
|
||||
</div>
|
||||
</div>
|
||||
</x-slot>
|
||||
|
||||
<div class="lp-wrap">
|
||||
<style>
|
||||
.lp-card{background:#fff;border-radius:16px;border:1.5px solid #f1f5f9;box-shadow:0 2px 8px rgba(0,0,0,.05);overflow:hidden;margin-bottom:14px;}
|
||||
.sec-hd{padding:11px 16px;border-bottom:1.5px solid #f1f5f9;background:#fafbfc;display:flex;align-items:center;justify-content:space-between;flex-wrap:wrap;gap:8px;}
|
||||
.sec-hd-l{display:flex;align-items:center;gap:8px;}
|
||||
.sec-bar{width:3px;height:15px;background:linear-gradient(180deg,#3b82f6,#2563eb);border-radius:4px;flex-shrink:0;}
|
||||
.sec-title{font-size:13px;font-weight:800;color:#1e293b;}
|
||||
.sec-count{font-size:11px;color:#94a3b8;background:#f1f5f9;padding:2px 8px;border-radius:20px;font-weight:600;white-space:nowrap;}
|
||||
|
||||
<div style="margin-bottom:18px;">
|
||||
<h1 style="font-size:20px;font-weight:900;color:#0f172a;line-height:1.2;letter-spacing:-.03em;">
|
||||
Laporan Penerima BLT-DD
|
||||
</h1>
|
||||
<p style="font-size:11.5px;color:#94a3b8;margin-top:4px;">
|
||||
Kelurahan Ngerong — warga yang telah ditetapkan sebagai penerima final BLT Dana Desa
|
||||
</p>
|
||||
</div>
|
||||
/* stat */
|
||||
.sum-grid{display:grid;grid-template-columns:repeat(4,1fr);gap:12px;margin-bottom:14px;}
|
||||
@media(max-width:860px){.sum-grid{grid-template-columns:1fr 1fr;}}
|
||||
@media(max-width:480px){.sum-grid{grid-template-columns:1fr 1fr;gap:8px;}}
|
||||
.sum-card{background:#fff;border-radius:16px;border:1.5px solid #f1f5f9;box-shadow:0 1px 6px rgba(0,0,0,.04);padding:14px 16px;}
|
||||
@media(max-width:480px){.sum-card{padding:12px 13px;border-radius:13px;}}
|
||||
.sum-icon{width:34px;height:34px;border-radius:10px;display:flex;align-items:center;justify-content:center;margin-bottom:8px;}
|
||||
.sum-icon svg{width:17px;height:17px;}
|
||||
.sum-val{font-size:22px;font-weight:900;color:#0f172a;line-height:1;letter-spacing:-.03em;}
|
||||
.sum-val-sm{font-size:14px;font-weight:900;color:#0f172a;line-height:1.3;letter-spacing:-.01em;}
|
||||
@media(max-width:480px){.sum-val{font-size:18px;}.sum-val-sm{font-size:12px;}}
|
||||
.sum-lbl{font-size:10px;color:#94a3b8;font-weight:700;margin-top:3px;text-transform:uppercase;letter-spacing:.04em;}
|
||||
@media(max-width:480px){.sum-lbl{font-size:9px;}}
|
||||
|
||||
/* export buttons */
|
||||
.exp-row{display:flex;flex-wrap:wrap;gap:8px;padding:14px 16px;align-items:center;}
|
||||
.exp-btn{display:inline-flex;align-items:center;gap:7px;padding:10px 18px;border-radius:12px;font-size:12.5px;font-weight:700;text-decoration:none;border:none;cursor:pointer;transition:filter .15s,transform .1s;line-height:1;white-space:nowrap;font-family:inherit;}
|
||||
.exp-btn svg{width:14px;height:14px;flex-shrink:0;}
|
||||
.exp-btn:hover{filter:brightness(.93);transform:translateY(-1px);}
|
||||
@media(max-width:480px){
|
||||
.exp-btn{padding:10px 14px;font-size:12px;border-radius:10px;flex:1;justify-content:center;}
|
||||
}
|
||||
.btn-pdf{background:linear-gradient(135deg,#ef4444,#dc2626);color:#fff;box-shadow:0 3px 12px rgba(220,38,38,.3);}
|
||||
.btn-excel{background:linear-gradient(135deg,#22c55e,#16a34a);color:#fff;box-shadow:0 3px 12px rgba(22,163,74,.3);}
|
||||
.btn-send{background:linear-gradient(135deg,#3b82f6,#2563eb);color:#fff;box-shadow:0 3px 12px rgba(37,99,235,.3);}
|
||||
.btn-gray{display:inline-flex;align-items:center;padding:8px 14px;border:none;border-radius:10px;background:#e5e7eb;color:#374151;font-size:12px;font-weight:700;cursor:pointer;font-family:inherit;}
|
||||
|
||||
/* upload form */
|
||||
.pub-wrap{padding:14px 16px;}
|
||||
.pub-form{display:grid;grid-template-columns:1fr 1fr auto;gap:10px;align-items:end;}
|
||||
@media(max-width:768px){.pub-form{grid-template-columns:1fr 1fr;}}
|
||||
@media(max-width:500px){.pub-form{grid-template-columns:1fr;}}
|
||||
@media(max-width:500px){.pub-form .exp-btn{justify-content:center;}}
|
||||
.pub-input{padding:10px 13px;border:1.5px solid #e2e8f0;border-radius:11px;font-size:16px;/* prevent iOS zoom */outline:none;background:#fff;color:#0f172a;width:100%;font-family:inherit;}
|
||||
.pub-input:focus{border-color:#93c5fd;box-shadow:0 0 0 3px rgba(37,99,235,.08);}
|
||||
.pub-list{margin-top:14px;display:flex;flex-direction:column;gap:10px;}
|
||||
.pub-item{display:flex;justify-content:space-between;align-items:center;gap:12px;padding:12px 14px;border:1.5px solid #f1f5f9;border-radius:13px;background:#fafbfc;flex-wrap:wrap;}
|
||||
.pub-title{font-size:13px;font-weight:700;color:#0f172a;}
|
||||
.pub-meta{font-size:11px;color:#94a3b8;margin-top:2px;}
|
||||
.pub-actions{display:flex;gap:8px;align-items:center;flex-wrap:wrap;}
|
||||
@media(max-width:480px){.pub-actions{width:100%;}}
|
||||
@media(max-width:480px){.pub-actions .exp-btn,.pub-actions .btn-gray{flex:1;justify-content:center;}}
|
||||
|
||||
/* desktop table */
|
||||
.lp-tbl-wrap{overflow-x:auto;-webkit-overflow-scrolling:touch;}
|
||||
table.lp-tbl{width:100%;border-collapse:collapse;min-width:1000px;}
|
||||
table.lp-tbl thead tr{background:#f8fafc;border-bottom:1.5px solid #f1f5f9;}
|
||||
table.lp-tbl thead th{padding:9px 11px;text-align:left;font-size:9.5px;font-weight:800;color:#94a3b8;text-transform:uppercase;letter-spacing:.07em;white-space:nowrap;}
|
||||
table.lp-tbl tbody tr{border-bottom:1px solid #f8fafc;transition:background .1s;}
|
||||
table.lp-tbl tbody tr:hover{background:#f0f7ff;}
|
||||
table.lp-tbl tbody tr:last-child{border-bottom:none;}
|
||||
table.lp-tbl tbody td{padding:9px 11px;vertical-align:middle;font-size:12px;color:#374151;}
|
||||
|
||||
/* mobile card */
|
||||
.mob-list{display:none;flex-direction:column;gap:10px;padding:14px;}
|
||||
@media(max-width:640px){
|
||||
.lp-tbl-wrap{display:none;}
|
||||
.mob-list{display:flex;}
|
||||
}
|
||||
.mob-card{background:#fafbfc;border:1.5px solid #f1f5f9;border-radius:13px;padding:13px;display:flex;flex-direction:column;gap:10px;}
|
||||
.mob-top{display:flex;align-items:center;gap:10px;}
|
||||
.mob-nomor{width:22px;height:22px;border-radius:6px;background:#f1f5f9;display:flex;align-items:center;justify-content:center;font-size:10px;font-weight:800;color:#94a3b8;flex-shrink:0;}
|
||||
.mob-name{font-size:13px;font-weight:800;color:#0f172a;flex:1;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;}
|
||||
.mob-rows{display:grid;grid-template-columns:1fr 1fr;gap:6px 10px;}
|
||||
.mob-kv{display:flex;flex-direction:column;gap:2px;}
|
||||
.mob-k{font-size:9px;font-weight:700;color:#94a3b8;text-transform:uppercase;letter-spacing:.05em;}
|
||||
.mob-v{font-size:11.5px;font-weight:600;color:#374151;}
|
||||
.mob-nik{font-family:'Courier New',monospace;font-size:10px;color:#64748b;background:#f1f5f9;padding:2px 5px;border-radius:4px;display:inline-block;}
|
||||
.mob-bottom{display:flex;align-items:center;justify-content:space-between;flex-wrap:wrap;gap:6px;padding-top:8px;border-top:1px solid #f1f5f9;}
|
||||
|
||||
/* shared */
|
||||
.av{width:30px;height:30px;border-radius:9px;background:linear-gradient(135deg,#3b82f6,#2563eb);display:flex;align-items:center;justify-content:center;font-size:11px;font-weight:800;color:#fff;flex-shrink:0;}
|
||||
.nik-pill{font-family:'Courier New',monospace;font-size:10px;color:#64748b;background:#f1f5f9;padding:2px 6px;border-radius:5px;}
|
||||
.rt-badge{display:inline-flex;padding:2px 7px;border-radius:6px;background:#eff6ff;color:#1d4ed8;font-size:10px;font-weight:800;border:1px solid #bfdbfe;}
|
||||
.prob-cell{display:flex;align-items:center;gap:6px;}
|
||||
.prob-bg{width:38px;height:4px;background:#f1f5f9;border-radius:99px;overflow:hidden;flex-shrink:0;}
|
||||
.prob-fill{height:100%;border-radius:99px;}
|
||||
.prob-txt{font-size:11px;font-weight:800;white-space:nowrap;}
|
||||
.st-pill{display:inline-flex;align-items:center;gap:4px;padding:4px 10px;border-radius:20px;font-size:10.5px;font-weight:700;}
|
||||
.st-dot{width:5px;height:5px;border-radius:50%;flex-shrink:0;}
|
||||
.bantuan-ya{display:inline-flex;padding:2px 8px;border-radius:20px;background:#fffbeb;color:#b45309;font-size:10px;font-weight:700;border:1px solid #fde68a;}
|
||||
.bantuan-tdk{display:inline-flex;padding:2px 8px;border-radius:20px;background:#f8fafc;color:#94a3b8;font-size:10px;font-weight:600;}
|
||||
.flash-ok{display:flex;align-items:center;gap:8px;background:#f0fdf4;border:1.5px solid #bbf7d0;color:#166534;padding:11px 16px;border-radius:13px;font-size:12.5px;font-weight:600;margin-bottom:14px;}
|
||||
.flash-ok svg{width:15px;height:15px;flex-shrink:0;}
|
||||
.empty-state{padding:52px 16px;text-align:center;}
|
||||
.empty-icon{width:46px;height:46px;background:#f1f5f9;border-radius:50%;display:flex;align-items:center;justify-content:center;margin:0 auto 10px;}
|
||||
.tbl-foot{padding:9px 16px;border-top:1.5px solid #f1f5f9;background:#fafbfc;text-align:center;}
|
||||
.credit{font-size:11px;color:#cbd5e1;font-weight:500;}
|
||||
</style>
|
||||
|
||||
{{-- FLASH --}}
|
||||
@if(session('success'))
|
||||
<div class="flash-ok">
|
||||
<svg fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2.5"
|
||||
d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z"/>
|
||||
</svg>
|
||||
<svg fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2.5" d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z"/></svg>
|
||||
{{ session('success') }}
|
||||
</div>
|
||||
@endif
|
||||
|
|
@ -99,52 +125,39 @@
|
|||
$periodes = $laporans->pluck('periode_bantuan')->filter()->unique()->count();
|
||||
@endphp
|
||||
|
||||
{{-- STAT CARDS --}}
|
||||
<div class="sum-grid">
|
||||
<div class="sum-card">
|
||||
<div class="sum-icon" style="background:#eff6ff;">
|
||||
<svg fill="none" stroke="#2563eb" 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>
|
||||
<svg fill="none" stroke="#2563eb" 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>
|
||||
<div class="sum-val">{{ $totalPenerima }}</div>
|
||||
<div class="sum-lbl">Total Penerima</div>
|
||||
</div>
|
||||
|
||||
<div class="sum-card">
|
||||
<div class="sum-icon" style="background:#f0fdf4;">
|
||||
<svg fill="none" stroke="#16a34a" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
|
||||
d="M12 8c-1.657 0-3 .895-3 2s1.343 2 3 2 3 .895 3 2-1.343 2-3 2m0-8c1.11 0 2.08.402 2.599 1M12 8V7m0 1v8m0 0v1m0-1c-1.11 0-2.08-.402-2.599-1M21 12a9 9 0 11-18 0 9 9 0 0118 0z"/>
|
||||
</svg>
|
||||
<svg fill="none" stroke="#16a34a" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 8c-1.657 0-3 .895-3 2s1.343 2 3 2 3 .895 3 2-1.343 2-3 2m0-8c1.11 0 2.08.402 2.599 1M12 8V7m0 1v8m0 0v1m0-1c-1.11 0-2.08-.402-2.599-1M21 12a9 9 0 11-18 0 9 9 0 0118 0z"/></svg>
|
||||
</div>
|
||||
<div class="sum-val sm">Rp {{ number_format($totalBantuan, 0, ',', '.') }}</div>
|
||||
<div class="sum-lbl">Total Dana Tersalurkan</div>
|
||||
<div class="sum-val-sm">Rp {{ number_format($totalBantuan,0,',','.') }}</div>
|
||||
<div class="sum-lbl">Total Dana</div>
|
||||
</div>
|
||||
|
||||
<div class="sum-card">
|
||||
<div class="sum-icon" style="background:#fffbeb;">
|
||||
<svg fill="none" stroke="#d97706" 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>
|
||||
<svg fill="none" stroke="#d97706" 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>
|
||||
<div class="sum-val">{{ number_format($avgProb, 1) }}<span style="font-size:14px;font-weight:700;">%</span></div>
|
||||
<div class="sum-lbl">Rata-rata Probabilitas</div>
|
||||
<div class="sum-val">{{ number_format($avgProb,1) }}<span style="font-size:13px;font-weight:700;">%</span></div>
|
||||
<div class="sum-lbl">Rata-rata Prob</div>
|
||||
</div>
|
||||
|
||||
<div class="sum-card">
|
||||
<div class="sum-icon" style="background:#fff1f2;">
|
||||
<svg fill="none" stroke="#e11d48" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
|
||||
d="M8 7V3m8 4V3m-9 8h10M5 21h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v12a2 2 0 002 2z"/>
|
||||
</svg>
|
||||
<svg fill="none" stroke="#e11d48" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 7V3m8 4V3m-9 8h10M5 21h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v12a2 2 0 002 2z"/></svg>
|
||||
</div>
|
||||
<div class="sum-val">{{ $periodes }}</div>
|
||||
<div class="sum-lbl">Periode Aktif</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{-- EKSPOR --}}
|
||||
<div class="lp-card">
|
||||
<div class="sec-hd">
|
||||
<div class="sec-hd-l">
|
||||
|
|
@ -152,24 +165,28 @@
|
|||
<span class="sec-title">Ekspor & Distribusi Data</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="exp-row">
|
||||
<a href="{{ route('admin.laporan.pdf') }}" class="exp-btn btn-pdf">Unduh PDF</a>
|
||||
<a href="{{ route('admin.laporan.excel') }}" class="exp-btn btn-excel">Unduh Excel</a>
|
||||
|
||||
<form action="{{ route('admin.laporan.kirim-ke-rt') }}" method="POST" style="margin:0;">
|
||||
<a href="{{ route('admin.laporan.pdf') }}" class="exp-btn btn-pdf">
|
||||
<svg fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 10v6m0 0l-3-3m3 3l3-3m2 8H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z"/></svg>
|
||||
Unduh PDF
|
||||
</a>
|
||||
<a href="{{ route('admin.laporan.excel') }}" class="exp-btn btn-excel">
|
||||
<svg fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z"/></svg>
|
||||
Unduh Excel
|
||||
</a>
|
||||
<form action="{{ route('admin.laporan.kirim-ke-rt') }}" method="POST" style="display:contents;">
|
||||
@csrf
|
||||
<button type="submit"
|
||||
onclick="return confirm('Kirim semua data penerima final ke RT dusun?')"
|
||||
class="exp-btn btn-send">
|
||||
Kirim ke RT Dusun
|
||||
<button type="submit" class="exp-btn btn-send"
|
||||
onclick="return confirm('Kirim semua data penerima final ke RT dusun?')">
|
||||
<svg fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 19l9 2-9-18-9 18 9-2zm0 0v-8"/></svg>
|
||||
Kirim ke RT
|
||||
</button>
|
||||
</form>
|
||||
|
||||
<span class="exp-meta">{{ $totalPenerima }} penerima terdaftar</span>
|
||||
<span style="font-size:11px;color:#94a3b8;width:100%;padding-top:2px;">{{ $totalPenerima }} penerima terdaftar</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{-- UPLOAD PDF PUBLIK --}}
|
||||
<div class="lp-card">
|
||||
<div class="sec-hd">
|
||||
<div class="sec-hd-l">
|
||||
|
|
@ -177,14 +194,15 @@ class="exp-btn btn-send">
|
|||
<span class="sec-title">Upload PDF Laporan Publik</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="pub-wrap">
|
||||
<form action="{{ route('admin.laporan.upload-publik-pdf') }}" method="POST" enctype="multipart/form-data" class="pub-form">
|
||||
<form action="{{ route('admin.laporan.upload-publik-pdf') }}" method="POST"
|
||||
enctype="multipart/form-data" class="pub-form">
|
||||
@csrf
|
||||
<input type="text" name="judul" placeholder="Judul laporan publik" required class="pub-input">
|
||||
<input type="file" name="file_pdf" accept="application/pdf" required class="pub-input">
|
||||
<button type="submit" class="exp-btn btn-send" style="justify-content:center;">
|
||||
Upload PDF Publik
|
||||
<input type="file" name="file_pdf" accept="application/pdf" required class="pub-input" style="font-size:13px;">
|
||||
<button type="submit" class="exp-btn btn-send">
|
||||
<svg fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 16v1a3 3 0 003 3h10a3 3 0 003-3v-1m-4-8l-4-4m0 0L8 8m4-4v12"/></svg>
|
||||
Upload
|
||||
</button>
|
||||
</form>
|
||||
|
||||
|
|
@ -192,24 +210,17 @@ class="exp-btn btn-send">
|
|||
<div class="pub-list">
|
||||
@foreach($laporanPubliks as $pdf)
|
||||
<div class="pub-item">
|
||||
<div>
|
||||
<div style="flex:1;min-width:0;">
|
||||
<div class="pub-title">{{ $pdf->judul }}</div>
|
||||
<div class="pub-meta">
|
||||
Diupload: {{ $pdf->created_at->format('d-m-Y H:i') }}
|
||||
</div>
|
||||
<div class="pub-meta">Diupload: {{ $pdf->created_at->format('d-m-Y H:i') }}</div>
|
||||
</div>
|
||||
|
||||
<div class="pub-actions">
|
||||
<a href="{{ asset('storage/' . $pdf->file_path) }}"
|
||||
target="_blank"
|
||||
class="exp-btn btn-pdf"
|
||||
style="padding:9px 14px;">
|
||||
<a href="{{ asset('storage/'.$pdf->file_path) }}" target="_blank" class="exp-btn btn-pdf" style="padding:8px 14px;">
|
||||
Lihat PDF
|
||||
</a>
|
||||
|
||||
<form action="{{ route('admin.laporan.hapus-publik-pdf', $pdf->id) }}" method="POST" onsubmit="return confirm('Hapus PDF publik ini?')">
|
||||
@csrf
|
||||
@method('DELETE')
|
||||
<form action="{{ route('admin.laporan.hapus-publik-pdf',$pdf->id) }}" method="POST"
|
||||
onsubmit="return confirm('Hapus PDF publik ini?')" style="display:contents;">
|
||||
@csrf @method('DELETE')
|
||||
<button type="submit" class="btn-gray">Hapus</button>
|
||||
</form>
|
||||
</div>
|
||||
|
|
@ -220,6 +231,7 @@ class="exp-btn btn-pdf"
|
|||
</div>
|
||||
</div>
|
||||
|
||||
{{-- DAFTAR PENERIMA FINAL --}}
|
||||
<div class="lp-card">
|
||||
<div class="sec-hd">
|
||||
<div class="sec-hd-l">
|
||||
|
|
@ -229,6 +241,7 @@ class="exp-btn btn-pdf"
|
|||
<span class="sec-count">{{ $totalPenerima }} data</span>
|
||||
</div>
|
||||
|
||||
{{-- DESKTOP TABLE --}}
|
||||
<div class="lp-tbl-wrap">
|
||||
<table class="lp-tbl">
|
||||
<thead>
|
||||
|
|
@ -243,109 +256,179 @@ class="exp-btn btn-pdf"
|
|||
<th>Penghasilan</th>
|
||||
<th>Tanggungan</th>
|
||||
<th>Aset</th>
|
||||
<th>Bantuan Lain</th>
|
||||
<th>Bantuan</th>
|
||||
<th>Usia</th>
|
||||
<th>Probabilitas</th>
|
||||
<th>Prob</th>
|
||||
<th>Status</th>
|
||||
<th>Tgl Penetapan</th>
|
||||
<th>Penetapan</th>
|
||||
<th>Periode</th>
|
||||
<th>Jumlah Bantuan</th>
|
||||
<th>Jumlah</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@forelse($laporans as $index => $item)
|
||||
@php
|
||||
$nama = $item->nama_lengkap ?? '-';
|
||||
$init = strtoupper(substr($nama, 0, 1));
|
||||
$init = strtoupper(substr($nama,0,1));
|
||||
$prob = $item->probability ?? 0;
|
||||
if ($prob <= 1) $prob = $prob * 100;
|
||||
|
||||
$sc = $prob >= 70 ? '#10b981' : ($prob >= 40 ? '#f59e0b' : '#f43f5e');
|
||||
$scBg = $prob >= 70 ? '#f0fdf4' : ($prob >= 40 ? '#fffbeb' : '#fff1f2');
|
||||
$scBd = $prob >= 70 ? '#bbf7d0' : ($prob >= 40 ? '#fde68a' : '#fecdd3');
|
||||
|
||||
$bantuanLain = strtolower($item->bantuan_lain ?? 'tidak');
|
||||
if($prob<=1) $prob=$prob*100;
|
||||
$sc = $prob>=70?'#10b981':($prob>=40?'#f59e0b':'#f43f5e');
|
||||
$scBg = $prob>=70?'#f0fdf4':($prob>=40?'#fffbeb':'#fff1f2');
|
||||
$scBd = $prob>=70?'#bbf7d0':($prob>=40?'#fde68a':'#fecdd3');
|
||||
@endphp
|
||||
|
||||
<tr>
|
||||
<td>{{ $index + 1 }}</td>
|
||||
|
||||
<td style="text-align:center;font-size:10.5px;color:#94a3b8;font-weight:700;">{{ $index+1 }}</td>
|
||||
<td>
|
||||
<div class="name-cell">
|
||||
<div style="display:flex;align-items:center;gap:8px;">
|
||||
<div class="av">{{ $init }}</div>
|
||||
<span class="name-txt">{{ $nama }}</span>
|
||||
<span style="font-size:12px;font-weight:700;color:#0f172a;white-space:nowrap;">{{ $nama }}</span>
|
||||
</div>
|
||||
</td>
|
||||
|
||||
<td><span class="nik-pill">{{ $item->nik ?? '-' }}</span></td>
|
||||
<td><span class="nik-pill">{{ $item->no_kk ?? '-' }}</span></td>
|
||||
|
||||
<td><span class="nik-pill">{{ $item->nik??'-' }}</span></td>
|
||||
<td><span class="nik-pill">{{ $item->no_kk??'-' }}</span></td>
|
||||
<td><span class="rt-badge">RT {{ str_pad($item->nomor_rt??'0',3,'0',STR_PAD_LEFT) }}</span></td>
|
||||
<td style="font-size:11.5px;white-space:nowrap;">{{ $item->nama_dusun??'-' }}</td>
|
||||
<td style="font-size:11.5px;">{{ $item->pekerjaan??'-' }}</td>
|
||||
<td style="font-size:12px;font-weight:600;white-space:nowrap;">Rp {{ number_format($item->penghasilan??0,0,',','.') }}</td>
|
||||
<td style="text-align:center;font-size:12px;">{{ $item->jumlah_tanggungan??0 }}</td>
|
||||
<td style="font-size:11.5px;">{{ $item->aset_kepemilikan??'-' }}</td>
|
||||
<td>
|
||||
<span class="rt-badge">
|
||||
RT {{ str_pad($item->nomor_rt ?? '0', 3, '0', STR_PAD_LEFT) }}
|
||||
</span>
|
||||
</td>
|
||||
|
||||
<td>{{ $item->nama_dusun ?? '-' }}</td>
|
||||
<td>{{ $item->pekerjaan ?? '-' }}</td>
|
||||
<td>Rp {{ number_format($item->penghasilan ?? 0, 0, ',', '.') }}</td>
|
||||
<td>{{ $item->jumlah_tanggungan ?? 0 }}</td>
|
||||
<td>{{ $item->aset_kepemilikan ?? '-' }}</td>
|
||||
|
||||
<td>
|
||||
@if($bantuanLain === 'ya')
|
||||
@if(strtolower($item->bantuan_lain??'')=='ya')
|
||||
<span class="bantuan-ya">Ya</span>
|
||||
@else
|
||||
<span class="bantuan-tdk">Tidak</span>
|
||||
@endif
|
||||
</td>
|
||||
|
||||
<td>{{ $item->usia ?? 0 }}</td>
|
||||
|
||||
<td style="text-align:center;font-size:12px;">{{ $item->usia??0 }}</td>
|
||||
<td>
|
||||
<div class="prob-cell">
|
||||
<div class="prob-bg">
|
||||
<div class="prob-fill" style="width:{{ min($prob, 100) }}%;background:{{ $sc }};"></div>
|
||||
</div>
|
||||
<span class="prob-txt" style="color:{{ $sc }};">{{ number_format($prob, 1) }}%</span>
|
||||
<div class="prob-bg"><div class="prob-fill" style="width:{{ min($prob,100) }}%;background:{{ $sc }};"></div></div>
|
||||
<span class="prob-txt" style="color:{{ $sc }};">{{ number_format($prob,1) }}%</span>
|
||||
</div>
|
||||
</td>
|
||||
|
||||
<td>
|
||||
<span class="st-pill" style="background:{{ $scBg }};color:{{ $sc }};border:1px solid {{ $scBd }};">
|
||||
<span class="st-dot" style="background:{{ $sc }};"></span>
|
||||
{{ ucfirst($item->status_verifikasi ?? '-') }}
|
||||
{{ ucfirst($item->status_verifikasi??'-') }}
|
||||
</span>
|
||||
</td>
|
||||
|
||||
<td>{{ $item->tanggal_penetapan?->format('d-m-Y') ?? '-' }}</td>
|
||||
<td>{{ $item->periode_bantuan ?? '-' }}</td>
|
||||
<td>Rp {{ number_format($item->jumlah_bantuan ?? 300000, 0, ',', '.') }}</td>
|
||||
<td style="font-size:11px;white-space:nowrap;">{{ $item->tanggal_penetapan?->format('d-m-Y')??'-' }}</td>
|
||||
<td style="font-size:11.5px;white-space:nowrap;">{{ $item->periode_bantuan??'-' }}</td>
|
||||
<td style="font-size:12px;font-weight:700;color:#166534;white-space:nowrap;">Rp {{ number_format($item->jumlah_bantuan??300000,0,',','.') }}</td>
|
||||
</tr>
|
||||
@empty
|
||||
<tr>
|
||||
<td colspan="17">
|
||||
<div class="empty-state">
|
||||
<div class="empty-icon">
|
||||
<svg fill="none" stroke="#d1d5db" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5"
|
||||
d="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z"/>
|
||||
</svg>
|
||||
</div>
|
||||
<p class="empty-txt">Belum ada data penerima final</p>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
<tr><td colspan="17">
|
||||
<div class="empty-state">
|
||||
<div class="empty-icon"><svg fill="none" stroke="#d1d5db" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" d="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z"/></svg></div>
|
||||
<p style="font-size:13px;font-weight:600;color:#94a3b8;">Belum ada data penerima final</p>
|
||||
</div>
|
||||
</td></tr>
|
||||
@endforelse
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<div class="tbl-foot">
|
||||
<span class="credit">SiBantuDes · Kelurahan Ngerong</span>
|
||||
{{-- MOBILE CARD LIST --}}
|
||||
<div class="mob-list">
|
||||
@forelse($laporans as $index => $item)
|
||||
@php
|
||||
$nama = $item->nama_lengkap ?? '-';
|
||||
$init = strtoupper(substr($nama,0,1));
|
||||
$prob = $item->probability ?? 0;
|
||||
if($prob<=1) $prob=$prob*100;
|
||||
$sc = $prob>=70?'#10b981':($prob>=40?'#f59e0b':'#f43f5e');
|
||||
$scBg = $prob>=70?'#f0fdf4':($prob>=40?'#fffbeb':'#fff1f2');
|
||||
$scBd = $prob>=70?'#bbf7d0':($prob>=40?'#fde68a':'#fecdd3');
|
||||
@endphp
|
||||
<div class="mob-card">
|
||||
{{-- Header --}}
|
||||
<div class="mob-top">
|
||||
<div class="mob-nomor">{{ $index+1 }}</div>
|
||||
<div class="av">{{ $init }}</div>
|
||||
<div class="mob-name">{{ $nama }}</div>
|
||||
</div>
|
||||
|
||||
{{-- NIK & KK --}}
|
||||
<div style="display:flex;gap:10px;flex-wrap:wrap;">
|
||||
<div class="mob-kv">
|
||||
<span class="mob-k">NIK</span>
|
||||
<span class="mob-nik">{{ $item->nik??'-' }}</span>
|
||||
</div>
|
||||
<div class="mob-kv">
|
||||
<span class="mob-k">No KK</span>
|
||||
<span class="mob-nik">{{ $item->no_kk??'-' }}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{-- Grid info --}}
|
||||
<div class="mob-rows">
|
||||
<div class="mob-kv">
|
||||
<span class="mob-k">RT</span>
|
||||
<span class="rt-badge" style="width:fit-content;">RT {{ str_pad($item->nomor_rt??'0',3,'0',STR_PAD_LEFT) }}</span>
|
||||
</div>
|
||||
<div class="mob-kv">
|
||||
<span class="mob-k">Dusun</span>
|
||||
<span class="mob-v">{{ $item->nama_dusun??'-' }}</span>
|
||||
</div>
|
||||
<div class="mob-kv">
|
||||
<span class="mob-k">Pekerjaan</span>
|
||||
<span class="mob-v">{{ $item->pekerjaan??'-' }}</span>
|
||||
</div>
|
||||
<div class="mob-kv">
|
||||
<span class="mob-k">Usia</span>
|
||||
<span class="mob-v">{{ $item->usia??0 }} thn</span>
|
||||
</div>
|
||||
<div class="mob-kv">
|
||||
<span class="mob-k">Penghasilan</span>
|
||||
<span class="mob-v">Rp {{ number_format($item->penghasilan??0,0,',','.') }}</span>
|
||||
</div>
|
||||
<div class="mob-kv">
|
||||
<span class="mob-k">Tanggungan</span>
|
||||
<span class="mob-v">{{ $item->jumlah_tanggungan??0 }} orang</span>
|
||||
</div>
|
||||
<div class="mob-kv">
|
||||
<span class="mob-k">Aset</span>
|
||||
<span class="mob-v">{{ $item->aset_kepemilikan??'-' }}</span>
|
||||
</div>
|
||||
<div class="mob-kv">
|
||||
<span class="mob-k">Bantuan Lain</span>
|
||||
@if(strtolower($item->bantuan_lain??'')=='ya')
|
||||
<span class="bantuan-ya" style="width:fit-content;">Ya</span>
|
||||
@else
|
||||
<span class="bantuan-tdk" style="width:fit-content;">Tidak</span>
|
||||
@endif
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{-- Bottom: prob + status + jumlah bantuan --}}
|
||||
<div class="mob-bottom">
|
||||
<div class="prob-cell">
|
||||
<div class="prob-bg"><div class="prob-fill" style="width:{{ min($prob,100) }}%;background:{{ $sc }};"></div></div>
|
||||
<span class="prob-txt" style="color:{{ $sc }};">{{ number_format($prob,1) }}%</span>
|
||||
</div>
|
||||
<span class="st-pill" style="background:{{ $scBg }};color:{{ $sc }};border:1px solid {{ $scBd }};">
|
||||
<span class="st-dot" style="background:{{ $sc }};"></span>{{ ucfirst($item->status_verifikasi??'-') }}
|
||||
</span>
|
||||
<span style="font-size:12px;font-weight:800;color:#166534;background:#f0fdf4;border:1px solid #bbf7d0;padding:3px 9px;border-radius:8px;white-space:nowrap;">
|
||||
Rp {{ number_format($item->jumlah_bantuan??300000,0,',','.') }}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
{{-- Periode & penetapan --}}
|
||||
<div style="font-size:10.5px;color:#94a3b8;">
|
||||
Periode: <strong style="color:#475569;">{{ $item->periode_bantuan??'-' }}</strong>
|
||||
• Penetapan: <strong style="color:#475569;">{{ $item->tanggal_penetapan?->format('d-m-Y')??'-' }}</strong>
|
||||
</div>
|
||||
</div>
|
||||
@empty
|
||||
<div class="empty-state">
|
||||
<div class="empty-icon"><svg fill="none" stroke="#d1d5db" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" d="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z"/></svg></div>
|
||||
<p style="font-size:13px;font-weight:600;color:#94a3b8;">Belum ada data penerima final</p>
|
||||
</div>
|
||||
@endforelse
|
||||
</div>
|
||||
|
||||
<div class="tbl-foot"><span class="credit">SiBantuDes · Kelurahan Ngerong</span></div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
</x-app-layout>
|
||||
</x-app-layout>
|
||||
|
|
@ -1,25 +1,650 @@
|
|||
<x-guest-layout>
|
||||
<div class="mb-4 text-sm text-gray-600">
|
||||
{{ __('Forgot your password? No problem. Just let us know your email address and we will email you a password reset link that will allow you to choose a new one.') }}
|
||||
<style>
|
||||
*, *::before, *::after { box-sizing: border-box; }
|
||||
|
||||
body {
|
||||
font-family: 'Plus Jakarta Sans', sans-serif;
|
||||
background: linear-gradient(145deg, #eff6ff 0%, #f8fafc 45%, #f0f9ff 100%);
|
||||
}
|
||||
|
||||
.fp-wrap {
|
||||
min-height: 100vh;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 24px;
|
||||
}
|
||||
|
||||
.fp-card {
|
||||
width: 100%;
|
||||
max-width: 460px;
|
||||
background: #ffffff;
|
||||
border-radius: 24px;
|
||||
padding: 32px 30px;
|
||||
box-shadow: 0 24px 60px rgba(15, 23, 42, 0.12);
|
||||
border: 1px solid rgba(226, 232, 240, 0.8);
|
||||
}
|
||||
|
||||
.fp-logo {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
margin-bottom: 18px;
|
||||
}
|
||||
|
||||
.fp-logo img {
|
||||
width: 56px;
|
||||
height: 56px;
|
||||
border-radius: 16px;
|
||||
object-fit: cover;
|
||||
box-shadow: 0 10px 24px rgba(37, 99, 235, 0.15);
|
||||
}
|
||||
|
||||
.fp-eyebrow {
|
||||
text-align: center;
|
||||
font-size: 11px;
|
||||
font-weight: 800;
|
||||
color: #2563eb;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: .08em;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.fp-title {
|
||||
text-align: center;
|
||||
font-size: 26px;
|
||||
font-weight: 900;
|
||||
color: #0f172a;
|
||||
letter-spacing: -.03em;
|
||||
line-height: 1.1;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.fp-sub {
|
||||
font-size: 13px;
|
||||
line-height: 1.7;
|
||||
color: #64748b;
|
||||
text-align: center;
|
||||
margin-bottom: 22px;
|
||||
}
|
||||
|
||||
/* Step indicator */
|
||||
.fp-steps {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 0;
|
||||
margin-bottom: 24px;
|
||||
}
|
||||
.fp-step {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
}
|
||||
.fp-step-dot {
|
||||
width: 28px;
|
||||
height: 28px;
|
||||
border-radius: 50%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: 11px;
|
||||
font-weight: 800;
|
||||
border: 2px solid #e2e8f0;
|
||||
color: #94a3b8;
|
||||
background: #f8fafc;
|
||||
transition: all .2s;
|
||||
}
|
||||
.fp-step-dot.active {
|
||||
background: #2563eb;
|
||||
border-color: #2563eb;
|
||||
color: #fff;
|
||||
box-shadow: 0 4px 12px rgba(37,99,235,.3);
|
||||
}
|
||||
.fp-step-dot.done {
|
||||
background: #10b981;
|
||||
border-color: #10b981;
|
||||
color: #fff;
|
||||
}
|
||||
.fp-step-label {
|
||||
font-size: 10px;
|
||||
font-weight: 600;
|
||||
color: #94a3b8;
|
||||
white-space: nowrap;
|
||||
}
|
||||
.fp-step-label.active { color: #2563eb; }
|
||||
.fp-step-label.done { color: #10b981; }
|
||||
.fp-step-line {
|
||||
width: 48px;
|
||||
height: 2px;
|
||||
background: #e2e8f0;
|
||||
margin: 0 4px;
|
||||
margin-bottom: 18px;
|
||||
border-radius: 99px;
|
||||
transition: background .2s;
|
||||
}
|
||||
.fp-step-line.done { background: #10b981; }
|
||||
|
||||
/* Alert boxes */
|
||||
.fp-alert {
|
||||
margin-bottom: 16px;
|
||||
padding: 11px 14px;
|
||||
border-radius: 12px;
|
||||
font-size: 12.5px;
|
||||
font-weight: 600;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
}
|
||||
.fp-alert svg { width: 15px; height: 15px; flex-shrink: 0; }
|
||||
.fp-alert.success { background: #f0fdf4; color: #166534; border: 1px solid #bbf7d0; }
|
||||
.fp-alert.error { background: #fff1f2; color: #9f1239; border: 1px solid #fecdd3; }
|
||||
|
||||
/* Fields */
|
||||
.fp-field { margin-bottom: 16px; }
|
||||
|
||||
.fp-label {
|
||||
display: block;
|
||||
margin-bottom: 7px;
|
||||
font-size: 12px;
|
||||
font-weight: 700;
|
||||
color: #334155;
|
||||
letter-spacing: .01em;
|
||||
}
|
||||
|
||||
.fp-input-wrap { position: relative; }
|
||||
|
||||
.fp-input-icon {
|
||||
position: absolute;
|
||||
left: 12px;
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
width: 30px;
|
||||
height: 30px;
|
||||
border-radius: 9px;
|
||||
background: #eff6ff;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
pointer-events: none;
|
||||
}
|
||||
.fp-input-icon svg { width: 14px; height: 14px; stroke: #2563eb; }
|
||||
|
||||
.fp-input {
|
||||
width: 100%;
|
||||
height: 50px;
|
||||
padding: 0 14px 0 52px;
|
||||
border: 1.5px solid #e2e8f0;
|
||||
border-radius: 13px;
|
||||
font-size: 16px; /* cegah auto-zoom iOS */
|
||||
color: #0f172a;
|
||||
background: #f8fafc;
|
||||
outline: none;
|
||||
font-family: inherit;
|
||||
transition: border-color .15s, box-shadow .15s, background .15s;
|
||||
}
|
||||
@media(min-width:640px){ .fp-input { font-size: 14px; } }
|
||||
|
||||
.fp-input:focus {
|
||||
border-color: #93c5fd;
|
||||
background: #fff;
|
||||
box-shadow: 0 0 0 3px rgba(37,99,235,.10);
|
||||
}
|
||||
.fp-input::placeholder { color: #cbd5e1; }
|
||||
.fp-input.is-error { border-color: #fca5a5; background: #fff1f2; }
|
||||
|
||||
.fp-input-toggle {
|
||||
position: absolute;
|
||||
right: 12px;
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
width: 28px;
|
||||
height: 28px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
cursor: pointer;
|
||||
color: #94a3b8;
|
||||
background: none;
|
||||
border: none;
|
||||
border-radius: 7px;
|
||||
transition: color .15s, background .15s;
|
||||
}
|
||||
.fp-input-toggle:hover { color: #475569; background: #f1f5f9; }
|
||||
.fp-input-toggle svg { width: 15px; height: 15px; }
|
||||
|
||||
.fp-hint {
|
||||
margin-top: 5px;
|
||||
font-size: 11px;
|
||||
color: #94a3b8;
|
||||
font-weight: 500;
|
||||
}
|
||||
.fp-hint.error { color: #dc2626; }
|
||||
|
||||
/* Strength bar */
|
||||
.fp-strength {
|
||||
margin-top: 6px;
|
||||
display: flex;
|
||||
gap: 4px;
|
||||
}
|
||||
.fp-strength-seg {
|
||||
flex: 1;
|
||||
height: 3px;
|
||||
border-radius: 99px;
|
||||
background: #e2e8f0;
|
||||
transition: background .2s;
|
||||
}
|
||||
|
||||
/* Submit button */
|
||||
.fp-btn {
|
||||
width: 100%;
|
||||
height: 50px;
|
||||
border: none;
|
||||
border-radius: 13px;
|
||||
background: linear-gradient(135deg, #3b82f6, #2563eb);
|
||||
color: #fff;
|
||||
font-size: 14px;
|
||||
font-weight: 800;
|
||||
letter-spacing: -.01em;
|
||||
cursor: pointer;
|
||||
font-family: inherit;
|
||||
box-shadow: 0 8px 20px rgba(37,99,235,.28);
|
||||
transition: transform .12s, box-shadow .12s;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 8px;
|
||||
}
|
||||
.fp-btn:hover { transform: translateY(-1px); box-shadow: 0 12px 28px rgba(37,99,235,.34); }
|
||||
.fp-btn:active { transform: scale(.98); }
|
||||
.fp-btn:disabled { opacity: .55; cursor: not-allowed; transform: none; }
|
||||
|
||||
.fp-footer {
|
||||
margin-top: 16px;
|
||||
text-align: center;
|
||||
font-size: 12.5px;
|
||||
color: #64748b;
|
||||
}
|
||||
.fp-footer a { color: #2563eb; text-decoration: none; font-weight: 700; }
|
||||
.fp-footer a:hover { text-decoration: underline; }
|
||||
|
||||
/* Section toggle */
|
||||
.fp-section { display: none; }
|
||||
.fp-section.visible { display: block; }
|
||||
|
||||
@media(max-width:480px){
|
||||
.fp-card { padding: 24px 18px; border-radius: 20px; }
|
||||
.fp-title { font-size: 22px; }
|
||||
}
|
||||
</style>
|
||||
|
||||
<div class="fp-wrap">
|
||||
<div class="fp-card">
|
||||
|
||||
<div class="fp-logo">
|
||||
<img src="{{ asset('favicon.ico') }}" alt="Logo SiBantuDes">
|
||||
</div>
|
||||
|
||||
<div class="fp-eyebrow">Reset Password</div>
|
||||
<div class="fp-title" id="fp-title">Lupa Password?</div>
|
||||
<div class="fp-sub" id="fp-sub">Masukkan email yang terdaftar untuk memulai reset password.</div>
|
||||
|
||||
{{-- Step indicator --}}
|
||||
<div class="fp-steps">
|
||||
<div class="fp-step">
|
||||
<div class="fp-step-dot active" id="dot-1">1</div>
|
||||
<div class="fp-step-label active" id="lbl-1">Verifikasi</div>
|
||||
</div>
|
||||
<div class="fp-step-line" id="line-1"></div>
|
||||
<div class="fp-step">
|
||||
<div class="fp-step-dot" id="dot-2">2</div>
|
||||
<div class="fp-step-label" id="lbl-2">Password Baru</div>
|
||||
</div>
|
||||
<div class="fp-step-line" id="line-2"></div>
|
||||
<div class="fp-step">
|
||||
<div class="fp-step-dot" id="dot-3">3</div>
|
||||
<div class="fp-step-label" id="lbl-3">Selesai</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{-- Alert area --}}
|
||||
<div id="fp-alert-area"></div>
|
||||
|
||||
{{-- SERVER-SIDE ERRORS --}}
|
||||
@if($errors->any())
|
||||
<div class="fp-alert error">
|
||||
<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>
|
||||
{{ $errors->first() }}
|
||||
</div>
|
||||
@endif
|
||||
|
||||
{{-- ═══ STEP 1: Verifikasi Email ═══ --}}
|
||||
<div class="fp-section visible" id="step-1">
|
||||
<div class="fp-field">
|
||||
<label class="fp-label" for="s1-email">Alamat Email</label>
|
||||
<div class="fp-input-wrap">
|
||||
<div class="fp-input-icon">
|
||||
<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 id="s1-email" type="email" class="fp-input"
|
||||
placeholder="nama@email.com" autocomplete="email" autofocus>
|
||||
</div>
|
||||
<div class="fp-hint" id="s1-email-hint"></div>
|
||||
</div>
|
||||
|
||||
<button class="fp-btn" id="btn-verify" onclick="verifyEmail()">
|
||||
<svg width="15" height="15" 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>
|
||||
Verifikasi Email
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{{-- ═══ STEP 2: Input Password Baru ═══ --}}
|
||||
<div class="fp-section" id="step-2">
|
||||
<div class="fp-field">
|
||||
<label class="fp-label">Password Baru</label>
|
||||
<div class="fp-input-wrap">
|
||||
<div class="fp-input-icon">
|
||||
<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 id="s2-password" type="password" class="fp-input"
|
||||
placeholder="Minimal 8 karakter" oninput="checkStrength()">
|
||||
<button type="button" class="fp-input-toggle" onclick="togglePw('s2-password', this)">
|
||||
<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>
|
||||
</button>
|
||||
</div>
|
||||
<div class="fp-strength" id="strength-bar">
|
||||
<div class="fp-strength-seg" id="seg1"></div>
|
||||
<div class="fp-strength-seg" id="seg2"></div>
|
||||
<div class="fp-strength-seg" id="seg3"></div>
|
||||
<div class="fp-strength-seg" id="seg4"></div>
|
||||
</div>
|
||||
<div class="fp-hint" id="s2-pw-hint"></div>
|
||||
</div>
|
||||
|
||||
<div class="fp-field">
|
||||
<label class="fp-label">Konfirmasi Password Baru</label>
|
||||
<div class="fp-input-wrap">
|
||||
<div class="fp-input-icon">
|
||||
<svg fill="none" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
|
||||
d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z"/>
|
||||
</svg>
|
||||
</div>
|
||||
<input id="s2-confirm" type="password" class="fp-input"
|
||||
placeholder="Ulangi password baru" oninput="checkMatch()">
|
||||
<button type="button" class="fp-input-toggle" onclick="togglePw('s2-confirm', this)">
|
||||
<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>
|
||||
</button>
|
||||
</div>
|
||||
<div class="fp-hint" id="s2-confirm-hint"></div>
|
||||
</div>
|
||||
|
||||
<button class="fp-btn" id="btn-reset" onclick="submitReset()">
|
||||
<svg width="15" height="15" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 13l4 4L19 7"/>
|
||||
</svg>
|
||||
Simpan Password Baru
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{{-- ═══ STEP 3: Selesai ═══ --}}
|
||||
<div class="fp-section" id="step-3" style="text-align:center;padding:10px 0;">
|
||||
<div style="width:60px;height:60px;border-radius:50%;background:#f0fdf4;border:2px solid #bbf7d0;display:flex;align-items:center;justify-content:center;margin:0 auto 14px;">
|
||||
<svg width="28" height="28" fill="none" stroke="#10b981" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2.5" d="M5 13l4 4L19 7"/>
|
||||
</svg>
|
||||
</div>
|
||||
<p style="font-size:15px;font-weight:800;color:#0f172a;margin-bottom:6px;">Password Berhasil Diubah!</p>
|
||||
<p style="font-size:13px;color:#64748b;line-height:1.6;margin-bottom:20px;">Password akun Anda sudah diperbarui. Silakan login menggunakan password baru.</p>
|
||||
<a href="{{ route('login') }}"
|
||||
style="display:inline-flex;align-items:center;justify-content:center;gap:8px;width:100%;height:50px;border-radius:13px;background:linear-gradient(135deg,#3b82f6,#2563eb);color:#fff;font-size:14px;font-weight:800;text-decoration:none;box-shadow:0 8px 20px rgba(37,99,235,.28);">
|
||||
<svg width="15" height="15" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M11 16l-4-4m0 0l4-4m-4 4h14"/>
|
||||
</svg>
|
||||
Kembali ke Login
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div class="fp-footer" id="fp-footer-link">
|
||||
Sudah ingat password?
|
||||
<a href="{{ route('login') }}">Kembali ke login</a>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Session Status -->
|
||||
<x-auth-session-status class="mb-4" :status="session('status')" />
|
||||
<script>
|
||||
var verifiedEmail = '';
|
||||
|
||||
<form method="POST" action="{{ route('password.email') }}">
|
||||
@csrf
|
||||
/* ── Toggle password visibility ── */
|
||||
function togglePw(inputId, btn) {
|
||||
var inp = document.getElementById(inputId);
|
||||
var showing = inp.type === 'text';
|
||||
inp.type = showing ? 'password' : 'text';
|
||||
btn.querySelector('svg').innerHTML = showing
|
||||
? '<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"/>';
|
||||
}
|
||||
|
||||
<!-- Email Address -->
|
||||
<div>
|
||||
<x-input-label for="email" :value="__('Email')" />
|
||||
<x-text-input id="email" class="block mt-1 w-full" type="email" name="email" :value="old('email')" required autofocus />
|
||||
<x-input-error :messages="$errors->get('email')" class="mt-2" />
|
||||
</div>
|
||||
/* ── Password strength ── */
|
||||
function checkStrength() {
|
||||
var v = document.getElementById('s2-password').value;
|
||||
var score = 0;
|
||||
if (v.length >= 8) score++;
|
||||
if (/[A-Z]/.test(v)) score++;
|
||||
if (/[0-9]/.test(v)) score++;
|
||||
if (/[^A-Za-z0-9]/.test(v)) score++;
|
||||
|
||||
<div class="flex items-center justify-end mt-4">
|
||||
<x-primary-button>
|
||||
{{ __('Email Password Reset Link') }}
|
||||
</x-primary-button>
|
||||
</div>
|
||||
</form>
|
||||
</x-guest-layout>
|
||||
var colors = ['#f43f5e','#f59e0b','#3b82f6','#10b981'];
|
||||
var labels = ['Sangat lemah','Cukup','Kuat','Sangat kuat'];
|
||||
for (var i = 1; i <= 4; i++) {
|
||||
var seg = document.getElementById('seg'+i);
|
||||
seg.style.background = i <= score ? colors[score-1] : '#e2e8f0';
|
||||
}
|
||||
var hint = document.getElementById('s2-pw-hint');
|
||||
hint.textContent = v.length ? labels[score-1] || '' : '';
|
||||
hint.className = 'fp-hint';
|
||||
checkMatch();
|
||||
}
|
||||
|
||||
function checkMatch() {
|
||||
var pw = document.getElementById('s2-password').value;
|
||||
var cfm = document.getElementById('s2-confirm').value;
|
||||
var hint = document.getElementById('s2-confirm-hint');
|
||||
if (!cfm) { hint.textContent = ''; return; }
|
||||
if (pw === cfm) {
|
||||
hint.textContent = '✓ Password cocok';
|
||||
hint.className = 'fp-hint';
|
||||
hint.style.color = '#10b981';
|
||||
} else {
|
||||
hint.textContent = 'Password tidak cocok';
|
||||
hint.className = 'fp-hint error';
|
||||
}
|
||||
}
|
||||
|
||||
/* ── Alert helper ── */
|
||||
function showAlert(type, msg) {
|
||||
var icon = type === 'success'
|
||||
? '<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"/>'
|
||||
: '<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 9v2m0 4h.01M12 3a9 9 0 100 18A9 9 0 0012 3z"/>';
|
||||
document.getElementById('fp-alert-area').innerHTML =
|
||||
'<div class="fp-alert '+type+'">' +
|
||||
'<svg fill="none" stroke="currentColor" viewBox="0 0 24 24">'+icon+'</svg>' +
|
||||
msg + '</div>';
|
||||
}
|
||||
function clearAlert() {
|
||||
document.getElementById('fp-alert-area').innerHTML = '';
|
||||
}
|
||||
|
||||
/* ── Step transition ── */
|
||||
function goStep(n) {
|
||||
[1,2,3].forEach(function(i){
|
||||
document.getElementById('step-'+i).classList.toggle('visible', i === n);
|
||||
});
|
||||
|
||||
/* Update step indicators */
|
||||
[1,2,3].forEach(function(i){
|
||||
var dot = document.getElementById('dot-'+i);
|
||||
var lbl = document.getElementById('lbl-'+i);
|
||||
dot.className = 'fp-step-dot ' + (i < n ? 'done' : i === n ? 'active' : '');
|
||||
lbl.className = 'fp-step-label ' + (i < n ? 'done' : i === n ? 'active' : '');
|
||||
if (i < n) dot.innerHTML = '✓';
|
||||
else dot.innerHTML = i;
|
||||
});
|
||||
if (document.getElementById('line-1'))
|
||||
document.getElementById('line-1').className = 'fp-step-line' + (n > 1 ? ' done' : '');
|
||||
if (document.getElementById('line-2'))
|
||||
document.getElementById('line-2').className = 'fp-step-line' + (n > 2 ? ' done' : '');
|
||||
|
||||
var titles = ['','Lupa Password?','Password Baru','Selesai!'];
|
||||
var subs = ['','Masukkan email yang terdaftar untuk memulai reset password.',
|
||||
'Buat password baru untuk akun <strong>'+verifiedEmail+'</strong>.',
|
||||
''];
|
||||
document.getElementById('fp-title').textContent = titles[n];
|
||||
document.getElementById('fp-sub').innerHTML = subs[n];
|
||||
document.getElementById('fp-footer-link').style.display = n === 3 ? 'none' : '';
|
||||
}
|
||||
|
||||
/* ── STEP 1: Verifikasi Email ── */
|
||||
function verifyEmail() {
|
||||
var email = document.getElementById('s1-email').value.trim();
|
||||
var hint = document.getElementById('s1-email-hint');
|
||||
|
||||
if (!email) {
|
||||
hint.textContent = 'Email wajib diisi.';
|
||||
hint.className = 'fp-hint error';
|
||||
return;
|
||||
}
|
||||
if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email)) {
|
||||
hint.textContent = 'Format email tidak valid.';
|
||||
hint.className = 'fp-hint error';
|
||||
return;
|
||||
}
|
||||
|
||||
hint.textContent = '';
|
||||
var btn = document.getElementById('btn-verify');
|
||||
btn.disabled = true;
|
||||
btn.innerHTML = '<svg width="15" height="15" fill="none" stroke="currentColor" viewBox="0 0 24 24" style="animation:spin .8s linear infinite"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 4v5h.582m15.356 2A8.001 8.001 0 004.582 9m0 0H9m11 11v-5h-.581m0 0a8.003 8.003 0 01-15.357-2m15.357 2H15"/></svg> Memverifikasi...';
|
||||
|
||||
var verifyBtnLabel = '<svg width="15" height="15" 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> Verifikasi Email';
|
||||
|
||||
fetch('{{ route("password.manual.check") }}', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'X-CSRF-TOKEN': '{{ csrf_token() }}',
|
||||
'Accept': 'application/json'
|
||||
},
|
||||
body: JSON.stringify({ email: email })
|
||||
})
|
||||
.then(function(r){
|
||||
return r.json().then(function(d){ return { ok: r.ok, data: d }; });
|
||||
})
|
||||
.then(function(res) {
|
||||
btn.disabled = false;
|
||||
btn.innerHTML = verifyBtnLabel;
|
||||
if (!res.ok) {
|
||||
showAlert('error', res.data.message || 'Terjadi kesalahan server. Coba lagi.');
|
||||
return;
|
||||
}
|
||||
if (res.data.found) {
|
||||
verifiedEmail = email;
|
||||
clearAlert();
|
||||
goStep(2);
|
||||
} else {
|
||||
showAlert('error', 'Email <strong>'+email+'</strong> tidak terdaftar dalam sistem.');
|
||||
}
|
||||
})
|
||||
.catch(function(){
|
||||
btn.disabled = false;
|
||||
btn.innerHTML = verifyBtnLabel;
|
||||
showAlert('error', 'Tidak dapat terhubung ke server. Periksa koneksi dan coba lagi.');
|
||||
});
|
||||
}
|
||||
|
||||
/* ── STEP 2: Submit Reset ── */
|
||||
function submitReset() {
|
||||
var pw = document.getElementById('s2-password').value;
|
||||
var cfm = document.getElementById('s2-confirm').value;
|
||||
|
||||
if (pw.length < 8) {
|
||||
document.getElementById('s2-pw-hint').textContent = 'Password minimal 8 karakter.';
|
||||
document.getElementById('s2-pw-hint').className = 'fp-hint error';
|
||||
return;
|
||||
}
|
||||
if (pw !== cfm) {
|
||||
document.getElementById('s2-confirm-hint').textContent = 'Password tidak cocok.';
|
||||
document.getElementById('s2-confirm-hint').className = 'fp-hint error';
|
||||
return;
|
||||
}
|
||||
|
||||
var btn = document.getElementById('btn-reset');
|
||||
btn.disabled = true;
|
||||
btn.innerHTML = '<svg width="15" height="15" fill="none" stroke="currentColor" viewBox="0 0 24 24" style="animation:spin .8s linear infinite"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 4v5h.582m15.356 2A8.001 8.001 0 004.582 9m0 0H9m11 11v-5h-.581m0 0a8.003 8.003 0 01-15.357-2m15.357 2H15"/></svg> Menyimpan...';
|
||||
|
||||
var resetBtnLabel = '<svg width="15" height="15" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 13l4 4L19 7"/></svg> Simpan Password Baru';
|
||||
|
||||
fetch('{{ route("password.manual.update") }}', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'X-CSRF-TOKEN': '{{ csrf_token() }}',
|
||||
'Accept': 'application/json'
|
||||
},
|
||||
body: JSON.stringify({ email: verifiedEmail, password: pw, password_confirmation: cfm })
|
||||
})
|
||||
.then(function(r){
|
||||
return r.json().then(function(d){ return { ok: r.ok, data: d }; });
|
||||
})
|
||||
.then(function(res) {
|
||||
btn.disabled = false;
|
||||
btn.innerHTML = resetBtnLabel;
|
||||
if (res.data.success) {
|
||||
clearAlert();
|
||||
goStep(3);
|
||||
} else {
|
||||
showAlert('error', res.data.message || 'Gagal menyimpan password. Coba lagi.');
|
||||
}
|
||||
})
|
||||
.catch(function(){
|
||||
btn.disabled = false;
|
||||
btn.innerHTML = resetBtnLabel;
|
||||
showAlert('error', 'Tidak dapat terhubung ke server. Periksa koneksi dan coba lagi.');
|
||||
});
|
||||
}
|
||||
|
||||
/* Enter key support */
|
||||
document.addEventListener('keydown', function(e){
|
||||
if (e.key !== 'Enter') return;
|
||||
var s1 = document.getElementById('step-1');
|
||||
var s2 = document.getElementById('step-2');
|
||||
if (s1.classList.contains('visible')) verifyEmail();
|
||||
else if (s2.classList.contains('visible')) submitReset();
|
||||
});
|
||||
|
||||
/* Spinner keyframe */
|
||||
var st = document.createElement('style');
|
||||
st.textContent = '@keyframes spin{to{transform:rotate(360deg)}}';
|
||||
document.head.appendChild(st);
|
||||
</script>
|
||||
</x-guest-layout>
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
|
|
@ -1,128 +1,292 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="{{ str_replace('_', '-', app()->getLocale()) }}">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<meta name="csrf-token" content="{{ csrf_token() }}">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<meta name="csrf-token" content="{{ csrf_token() }}">
|
||||
<title>{{ config('app.name', 'Laravel') }}</title>
|
||||
<link rel="icon" type="image/png" href="{{ asset('Lambang_Kabupaten_Pasuruan.png') }}">
|
||||
<link rel="preconnect" href="https://fonts.bunny.net">
|
||||
<link href="https://fonts.bunny.net/css?family=figtree:400,500,600&display=swap" rel="stylesheet"/>
|
||||
@vite(['resources/css/app.css', 'resources/js/app.js'])
|
||||
<style>
|
||||
*,*::before,*::after{box-sizing:border-box;margin:0;padding:0;}
|
||||
body{font-family:'Figtree',sans-serif;background:#f1f5f9;min-height:100dvh;-webkit-font-smoothing:antialiased;}
|
||||
|
||||
<title>{{ config('app.name', 'Laravel') }}</title>
|
||||
/* ══ NAVBAR ══ */
|
||||
#nb-wrap{
|
||||
position:sticky;top:0;z-index:50;
|
||||
padding:10px 16px 0;
|
||||
transition:padding .25s;
|
||||
}
|
||||
#nb-wrap.scrolled{ padding-top:0; }
|
||||
|
||||
<link rel="icon" type="image/png" href="{{ asset('Lambang_Kabupaten_Pasuruan.png') }}">
|
||||
<link rel="preconnect" href="https://fonts.bunny.net">
|
||||
<link href="https://fonts.bunny.net/css?family=figtree:400,500,600&display=swap" rel="stylesheet" />
|
||||
#nb-shell{
|
||||
max-width:80rem;margin:0 auto;
|
||||
background:rgba(255,255,255,.96);
|
||||
backdrop-filter:blur(14px);-webkit-backdrop-filter:blur(14px);
|
||||
border:1.5px solid #e2e8f0;
|
||||
border-radius:16px;
|
||||
box-shadow:0 4px 24px rgba(15,23,42,.07);
|
||||
padding:10px 18px;
|
||||
transition:border-radius .25s,box-shadow .25s;
|
||||
will-change:transform;
|
||||
transform:translateZ(0);
|
||||
}
|
||||
#nb-wrap.scrolled #nb-shell{
|
||||
border-radius:0 0 16px 16px;
|
||||
border-top:none;
|
||||
box-shadow:0 8px 32px rgba(15,23,42,.10);
|
||||
}
|
||||
|
||||
@vite(['resources/css/app.css', 'resources/js/app.js'])
|
||||
</head>
|
||||
<body class="font-sans antialiased">
|
||||
<div class="min-h-screen bg-gray-100">
|
||||
.nb-row{ display:flex;align-items:center;gap:10px; }
|
||||
|
||||
<div class="bg-white border-b border-gray-200 sticky top-0 z-50 shadow-sm">
|
||||
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-3">
|
||||
<div class="flex items-center justify-between gap-4">
|
||||
/* ── LINKS ── */
|
||||
.nb-links{ display:flex;align-items:center;gap:5px;flex:1; }
|
||||
|
||||
@php
|
||||
$role = Auth::user()->role ?? null;
|
||||
@endphp
|
||||
.nb-links a{
|
||||
display:inline-flex;align-items:center;
|
||||
padding:7px 13px;border-radius:10px;
|
||||
font-size:12.5px;font-weight:600;
|
||||
color:#64748b;background:#f8fafc;
|
||||
border:1.5px solid transparent;
|
||||
text-decoration:none;white-space:nowrap;
|
||||
transition:background .15s,color .15s,border-color .15s,box-shadow .15s,
|
||||
max-width .2s ease, opacity .2s ease, padding .2s ease, margin .2s ease;
|
||||
max-width:200px;
|
||||
overflow:hidden;
|
||||
opacity:1;
|
||||
will-change:max-width, opacity;
|
||||
}
|
||||
.nb-links a:hover{ background:#fff;border-color:#e2e8f0;color:#1e293b; }
|
||||
.nb-links a.nb-active{
|
||||
background:#2563eb;color:#fff;
|
||||
border-color:#2563eb;
|
||||
box-shadow:0 3px 10px rgba(37,99,235,.25);
|
||||
}
|
||||
|
||||
<div class="flex items-center gap-2 flex-1">
|
||||
.nb-links.hide-inactive a:not(.nb-active){
|
||||
max-width:0;opacity:0;
|
||||
padding-left:0;padding-right:0;
|
||||
margin:0;border-width:0;
|
||||
pointer-events:none;
|
||||
}
|
||||
|
||||
@if($role === 'admin')
|
||||
<a href="{{ route('admin.dashboard') }}"
|
||||
class="flex-1 text-center px-4 py-2 rounded-xl text-sm font-medium border
|
||||
{{ request()->routeIs('admin.dashboard') ? 'bg-blue-600 text-white border-blue-600' : 'bg-white text-gray-700 border-gray-200 hover:bg-gray-50' }}">
|
||||
Dashboard
|
||||
</a>
|
||||
/* ── USER ── */
|
||||
.nb-user{ display:flex;align-items:center;gap:8px;flex-shrink:0; }
|
||||
.nb-avatar{
|
||||
width:32px;height:32px;border-radius:50%;
|
||||
background:linear-gradient(135deg,#3b82f6,#2563eb);
|
||||
display:flex;align-items:center;justify-content:center;
|
||||
color:#fff;font-size:12px;font-weight:800;
|
||||
box-shadow:0 2px 8px rgba(37,99,235,.22);flex-shrink:0;
|
||||
}
|
||||
.nb-name{ font-size:12.5px;font-weight:600;color:#374151;white-space:nowrap; }
|
||||
.nb-logout{
|
||||
font-size:12px;font-weight:700;color:#ef4444;
|
||||
background:#fff1f2;border:1.5px solid #fecdd3;
|
||||
padding:5px 12px;border-radius:9px;cursor:pointer;
|
||||
transition:background .15s;white-space:nowrap;
|
||||
}
|
||||
.nb-logout:hover{ background:#ffe4e6; }
|
||||
|
||||
<a href="{{ route('admin.data-warga') }}"
|
||||
class="flex-1 text-center px-4 py-2 rounded-xl text-sm font-medium border
|
||||
{{ request()->routeIs('admin.data-warga') ? 'bg-blue-600 text-white border-blue-600' : 'bg-white text-gray-700 border-gray-200 hover:bg-gray-50' }}">
|
||||
Data Warga
|
||||
</a>
|
||||
/* ══ MOBILE ══ */
|
||||
@media(max-width:768px){
|
||||
#nb-wrap{ padding:8px 12px 0; }
|
||||
#nb-shell{ padding:9px 13px; }
|
||||
.nb-row{ flex-direction:column;align-items:stretch;gap:8px; }
|
||||
.nb-links{
|
||||
overflow-x:auto;-webkit-overflow-scrolling:touch;
|
||||
scrollbar-width:none;gap:5px;padding-bottom:1px;
|
||||
}
|
||||
.nb-links::-webkit-scrollbar{ display:none; }
|
||||
.nb-links.hide-inactive a:not(.nb-active){
|
||||
max-width:0;opacity:0;
|
||||
padding-left:0;padding-right:0;
|
||||
margin:0;border-width:0;pointer-events:none;
|
||||
}
|
||||
.nb-user{ justify-content:space-between;width:100%; }
|
||||
.nb-name{ display:none; }
|
||||
}
|
||||
@media(max-width:400px){
|
||||
.nb-links a{ font-size:11.5px;padding:6px 10px; }
|
||||
.nb-logout{ font-size:11.5px;padding:5px 10px; }
|
||||
}
|
||||
|
||||
<a href="{{ route('admin.data-akun') }}"
|
||||
class="flex-1 text-center px-4 py-2 rounded-xl text-sm font-medium border
|
||||
{{ request()->routeIs('admin.data-akun') ? 'bg-blue-600 text-white border-blue-600' : 'bg-white text-gray-700 border-gray-200 hover:bg-gray-50' }}">
|
||||
Data Akun
|
||||
</a>
|
||||
/* ══ LAYOUT ══ */
|
||||
.page-header{ max-width:80rem;margin:14px auto 0;padding:0 16px; }
|
||||
.page-header-inner{
|
||||
background:#fff;border:1.5px solid #e2e8f0;border-radius:16px;
|
||||
box-shadow:0 2px 12px rgba(15,23,42,.05);padding:14px 18px;
|
||||
}
|
||||
.page-main{
|
||||
max-width:80rem;margin:14px auto 24px;padding:0 16px;
|
||||
display:flex;flex-direction:column;gap:14px;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<a href="{{ route('admin.filterisasi') }}"
|
||||
class="flex-1 text-center px-4 py-2 rounded-xl text-sm font-medium border
|
||||
{{ request()->routeIs('admin.filterisasi') ? 'bg-blue-600 text-white border-blue-600' : 'bg-white text-gray-700 border-gray-200 hover:bg-gray-50' }}">
|
||||
Filterisasi
|
||||
</a>
|
||||
<!-- ══ NAVBAR ══ -->
|
||||
<div id="nb-wrap">
|
||||
<div id="nb-shell">
|
||||
<div class="nb-row">
|
||||
|
||||
<a href="{{ route('admin.laporan') }}"
|
||||
class="flex-1 text-center px-4 py-2 rounded-xl text-sm font-medium border
|
||||
{{ request()->routeIs('admin.laporan') ? 'bg-blue-600 text-white border-blue-600' : 'bg-white text-gray-700 border-gray-200 hover:bg-gray-50' }}">
|
||||
Laporan
|
||||
</a>
|
||||
@php $role = Auth::user()->role ?? null; @endphp
|
||||
|
||||
@else
|
||||
<a href="{{ route('dashboard') }}"
|
||||
class="flex-1 text-center px-4 py-2 rounded-xl text-sm font-medium border
|
||||
{{ request()->routeIs('dashboard') ? 'bg-blue-600 text-white border-blue-600' : 'bg-white text-gray-700 border-gray-200 hover:bg-gray-50' }}">
|
||||
Dashboard
|
||||
</a>
|
||||
|
||||
<a href="{{ route('rt.calon-penerima.create') }}"
|
||||
class="flex-1 text-center px-4 py-2 rounded-xl text-sm font-medium border
|
||||
{{ request()->routeIs('rt.calon-penerima.create') ? 'bg-blue-600 text-white border-blue-600' : 'bg-white text-gray-700 border-gray-200 hover:bg-gray-50' }}">
|
||||
Pendataan
|
||||
</a>
|
||||
|
||||
<a href="{{ route('rt.calon-penerima.index') }}"
|
||||
class="flex-1 text-center px-4 py-2 rounded-xl text-sm font-medium border
|
||||
{{ request()->routeIs('rt.calon-penerima.index') ? 'bg-blue-600 text-white border-blue-600' : 'bg-white text-gray-700 border-gray-200 hover:bg-gray-50' }}">
|
||||
Riwayat
|
||||
</a>
|
||||
|
||||
<a href="{{ route('rt.laporan.index') }}"
|
||||
class="flex-1 text-center px-4 py-2 rounded-xl text-sm font-medium border
|
||||
{{ request()->routeIs('rt.laporan.*') ? 'bg-blue-600 text-white border-blue-600' : 'bg-white text-gray-700 border-gray-200 hover:bg-gray-50' }}">
|
||||
Laporan
|
||||
</a>
|
||||
@endif
|
||||
|
||||
</div>
|
||||
|
||||
<div class="flex items-center gap-3">
|
||||
<div class="w-9 h-9 rounded-full bg-gray-200 flex items-center justify-center overflow-hidden">
|
||||
<span class="text-sm font-semibold text-gray-700">
|
||||
{{ strtoupper(substr(Auth::user()->name ?? 'U', 0, 1)) }}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div class="text-sm text-gray-700 whitespace-nowrap">
|
||||
{{ Auth::user()->name ?? '' }}
|
||||
</div>
|
||||
|
||||
<form method="POST" action="{{ route('logout') }}">
|
||||
@csrf
|
||||
<button type="submit" class="text-sm text-red-500 hover:text-red-600">
|
||||
Keluar
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
<!-- LINKS -->
|
||||
<nav class="nb-links" id="nb-links">
|
||||
@if($role === 'admin')
|
||||
<a href="{{ route('admin.dashboard') }}"
|
||||
class="{{ request()->routeIs('admin.dashboard') ? 'nb-active' : '' }}">Dashboard</a>
|
||||
<a href="{{ route('admin.data-warga') }}"
|
||||
class="{{ request()->routeIs('admin.data-warga') ? 'nb-active' : '' }}">Data Warga</a>
|
||||
<a href="{{ route('admin.data-akun') }}"
|
||||
class="{{ request()->routeIs('admin.data-akun') ? 'nb-active' : '' }}">Data Akun</a>
|
||||
<a href="{{ route('admin.filterisasi') }}"
|
||||
class="{{ request()->routeIs('admin.filterisasi') ? 'nb-active' : '' }}">Filterisasi</a>
|
||||
<a href="{{ route('admin.laporan') }}"
|
||||
class="{{ request()->routeIs('admin.laporan') ? 'nb-active' : '' }}">Laporan</a>
|
||||
@else
|
||||
<a href="{{ route('dashboard') }}"
|
||||
class="{{ request()->routeIs('dashboard') ? 'nb-active' : '' }}">Dashboard</a>
|
||||
<a href="{{ route('rt.calon-penerima.create') }}"
|
||||
class="{{ request()->routeIs('rt.calon-penerima.create') ? 'nb-active' : '' }}">Pendataan</a>
|
||||
<a href="{{ route('rt.calon-penerima.index') }}"
|
||||
class="{{ request()->routeIs('rt.calon-penerima.index') ? 'nb-active' : '' }}">Riwayat</a>
|
||||
<a href="{{ route('rt.laporan.index') }}"
|
||||
class="{{ request()->routeIs('rt.laporan.*') ? 'nb-active' : '' }}">Laporan</a>
|
||||
@endif
|
||||
</nav>
|
||||
|
||||
<!-- USER -->
|
||||
<div class="nb-user">
|
||||
<div style="display:flex;align-items:center;gap:8px;">
|
||||
<div class="nb-avatar">{{ strtoupper(substr(Auth::user()->name ?? 'U', 0, 1)) }}</div>
|
||||
<span class="nb-name">{{ Auth::user()->name ?? '' }}</span>
|
||||
</div>
|
||||
<form method="POST" action="{{ route('logout') }}">
|
||||
@csrf
|
||||
<button type="submit" class="nb-logout">Keluar</button>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
@isset($header)
|
||||
<header class="bg-white shadow">
|
||||
<div class="max-w-7xl mx-auto py-6 px-4 sm:px-6 lg:px-8">
|
||||
{{ $header }}
|
||||
</div>
|
||||
</header>
|
||||
@endisset
|
||||
|
||||
<main>
|
||||
<div class="py-8">
|
||||
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
|
||||
{{ $slot }}
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
</div>
|
||||
</body>
|
||||
</div>
|
||||
|
||||
<!-- HEADER SLOT -->
|
||||
@isset($header)
|
||||
<div class="page-header">
|
||||
<div class="page-header-inner">{{ $header }}</div>
|
||||
</div>
|
||||
@endisset
|
||||
|
||||
<!-- MAIN -->
|
||||
<main class="page-main">{{ $slot }}</main>
|
||||
|
||||
<script>
|
||||
(function(){
|
||||
var wrap = document.getElementById('nb-wrap');
|
||||
var links = document.getElementById('nb-links');
|
||||
|
||||
/* ── scrolled class: IntersectionObserver, zero reflow ── */
|
||||
var sentinel = document.createElement('div');
|
||||
sentinel.style.cssText = 'position:absolute;top:1px;left:0;width:1px;height:1px;pointer-events:none;';
|
||||
document.body.prepend(sentinel);
|
||||
new IntersectionObserver(function(e){
|
||||
e[0].isIntersecting
|
||||
? wrap.classList.remove('scrolled')
|
||||
: wrap.classList.add('scrolled');
|
||||
}, {threshold:0}).observe(sentinel);
|
||||
|
||||
/*
|
||||
* ── hide-inactive: DESKTOP ONLY ──
|
||||
*
|
||||
* Di mobile (≤768px) fitur ini dimatikan total karena:
|
||||
* - Konten halaman pendek → scrollY sering jitter di bawah
|
||||
* - iOS rubber-band / Android overscroll bounce bikin scrollY
|
||||
* naik-turun ±2-5px meski jari tidak bergerak → class toggle
|
||||
* bolak-balik tiap frame → kedip/getar
|
||||
* - Di mobile sudah ada horizontal scroll untuk nav links,
|
||||
* jadi collapse tidak diperlukan
|
||||
*/
|
||||
var MOBILE_BP = 768;
|
||||
|
||||
var lastY = window.scrollY;
|
||||
var peakY = window.scrollY;
|
||||
var hidden = false;
|
||||
var rafPending = null;
|
||||
|
||||
var DEAD_ZONE = 8;
|
||||
var UP_THRESHOLD = 32;
|
||||
var BOTTOM_GUARD = 12;
|
||||
|
||||
function isMobile(){ return window.innerWidth <= MOBILE_BP; }
|
||||
|
||||
function maxScroll(){
|
||||
return Math.max(0, document.documentElement.scrollHeight - window.innerHeight);
|
||||
}
|
||||
|
||||
function applyHide(val){
|
||||
if(val === hidden) return;
|
||||
hidden = val;
|
||||
if(val) links.classList.add('hide-inactive');
|
||||
else links.classList.remove('hide-inactive');
|
||||
}
|
||||
|
||||
function tick(){
|
||||
rafPending = null;
|
||||
|
||||
/* Mobile: selalu tampilkan semua link, jangan collapse */
|
||||
if(isMobile()){
|
||||
applyHide(false);
|
||||
lastY = window.scrollY;
|
||||
peakY = window.scrollY;
|
||||
return;
|
||||
}
|
||||
|
||||
var y = window.scrollY;
|
||||
var diff = y - lastY;
|
||||
|
||||
if(y > peakY) peakY = y;
|
||||
|
||||
/* Bottom guard */
|
||||
if(y >= maxScroll() - BOTTOM_GUARD){
|
||||
if(y > 50) applyHide(true);
|
||||
lastY = y;
|
||||
return;
|
||||
}
|
||||
|
||||
/* Dead zone */
|
||||
if(Math.abs(diff) < DEAD_ZONE){
|
||||
lastY = y;
|
||||
return;
|
||||
}
|
||||
|
||||
if(diff > 0 && y > 50){
|
||||
applyHide(true);
|
||||
} else if(diff < 0 && hidden){
|
||||
if(peakY - y >= UP_THRESHOLD){
|
||||
applyHide(false);
|
||||
peakY = y;
|
||||
}
|
||||
}
|
||||
|
||||
lastY = y;
|
||||
}
|
||||
|
||||
window.addEventListener('scroll', function(){
|
||||
if(rafPending) return;
|
||||
rafPending = requestAnimationFrame(tick);
|
||||
}, {passive:true});
|
||||
|
||||
/* Resize: kalau user putar layar dari landscape ke portrait, reset state */
|
||||
window.addEventListener('resize', function(){
|
||||
if(isMobile()) applyHide(false);
|
||||
}, {passive:true});
|
||||
|
||||
})();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
|
@ -1,30 +1,40 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="{{ str_replace('_', '-', app()->getLocale()) }}">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<meta name="csrf-token" content="{{ csrf_token() }}">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<meta name="csrf-token" content="{{ csrf_token() }}">
|
||||
|
||||
<title>{{ config('app.name', 'Laravel') }}</title>
|
||||
<!-- ✅ GANTI TITLE -->
|
||||
<title>SiBantuDes</title>
|
||||
|
||||
<!-- Fonts -->
|
||||
<link rel="preconnect" href="https://fonts.bunny.net">
|
||||
<link href="https://fonts.bunny.net/css?family=figtree:400,500,600&display=swap" rel="stylesheet" />
|
||||
<!-- Fonts -->
|
||||
<link rel="preconnect" href="https://fonts.bunny.net">
|
||||
<link href="https://fonts.bunny.net/css?family=figtree:400,500,600&display=swap" rel="stylesheet" />
|
||||
|
||||
<!-- Scripts -->
|
||||
@vite(['resources/css/app.css', 'resources/js/app.js'])
|
||||
</head>
|
||||
<body class="font-sans text-gray-900 antialiased">
|
||||
<div class="min-h-screen flex flex-col sm:justify-center items-center pt-6 sm:pt-0 bg-gray-100">
|
||||
<div>
|
||||
<a href="/">
|
||||
<x-application-logo class="w-20 h-20 fill-current text-gray-500" />
|
||||
</a>
|
||||
</div>
|
||||
<!-- Scripts -->
|
||||
@vite(['resources/css/app.css', 'resources/js/app.js'])
|
||||
</head>
|
||||
|
||||
<div class="w-full sm:max-w-md mt-6 px-6 py-4 bg-white shadow-md overflow-hidden sm:rounded-lg">
|
||||
{{ $slot }}
|
||||
</div>
|
||||
<body class="font-sans text-gray-900 antialiased">
|
||||
|
||||
<!-- ✅ HAPUS LOGO LARAVEL -->
|
||||
<div class="min-h-screen flex flex-col justify-center items-center bg-gray-100">
|
||||
|
||||
<!-- ❌ INI DIHAPUS
|
||||
<div>
|
||||
<a href="/">
|
||||
<x-application-logo class="w-20 h-20 fill-current text-gray-500" />
|
||||
</a>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
-->
|
||||
|
||||
<!-- ✅ ISI HALAMAN (LOGIN / FORGOT PASSWORD) -->
|
||||
<div class="w-full sm:max-w-md px-6 py-4 bg-white shadow-md overflow-hidden sm:rounded-lg">
|
||||
{{ $slot }}
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
|
|
@ -6,7 +6,22 @@
|
|||
</div>
|
||||
</x-slot>
|
||||
|
||||
{{-- TAMPILKAN ERROR VALIDASI --}}
|
||||
<style>
|
||||
.input-field { font-size: 16px !important; }
|
||||
@media (min-width: 640px) { .input-field { font-size: 14px !important; } }
|
||||
|
||||
@media (max-width: 480px) {
|
||||
.action-row { flex-direction: column-reverse !important; }
|
||||
.action-row a,
|
||||
.action-row button { width: 100%; justify-content: center; text-align: center; }
|
||||
}
|
||||
|
||||
@media (max-width: 640px) {
|
||||
.section-body { padding: 14px 14px !important; }
|
||||
.section-head { padding: 10px 14px !important; }
|
||||
}
|
||||
</style>
|
||||
|
||||
@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>
|
||||
|
|
@ -18,31 +33,29 @@
|
|||
</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
|
||||
|
||||
<form action="{{ route('rt.calon-penerima.store') }}" method="POST" id="formPendataan" novalidate>
|
||||
<form action="{{ route('rt.calon-penerima.store') }}" method="POST" id="formPendataan" novalidate enctype="multipart/form-data">
|
||||
@csrf
|
||||
|
||||
{{-- 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">
|
||||
<div class="section-head 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>
|
||||
|
||||
<div class="p-5 grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-4">
|
||||
<div class="section-body p-5 grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-4">
|
||||
@php
|
||||
$myRtId = auth()->user()->rt_id ?? (auth()->user()->rt->id ?? null);
|
||||
@endphp
|
||||
|
|
@ -51,8 +64,6 @@
|
|||
<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"
|
||||
|
|
@ -67,136 +78,87 @@ class="input-field w-full border border-gray-200 rounded-xl px-3 py-2 text-sm fo
|
|||
</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"
|
||||
<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 inputmode="numeric">
|
||||
<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"
|
||||
<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 inputmode="numeric">
|
||||
<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"
|
||||
<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"
|
||||
>
|
||||
<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>
|
||||
</div>
|
||||
|
||||
{{-- 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"
|
||||
<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"
|
||||
<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"
|
||||
<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 inputmode="numeric">
|
||||
<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"
|
||||
>
|
||||
<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>
|
||||
|
|
@ -210,25 +172,19 @@ class="input-field w-full border border-gray-200 rounded-xl px-3 py-2 text-sm fo
|
|||
|
||||
{{-- SECTION 2: Data Tempat Tinggal --}}
|
||||
<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">
|
||||
<div class="section-head 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-1 md:grid-cols-2 lg:grid-cols-4 gap-4">
|
||||
{{-- ALAMAT --}}
|
||||
<div class="section-body p-5 grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-4">
|
||||
<div class="field-group lg:col-span-2">
|
||||
<label class="block text-xs font-semibold text-gray-600 mb-1">
|
||||
Alamat <span class="text-red-500">*</span>
|
||||
</label>
|
||||
<input
|
||||
type="text"
|
||||
name="alamat"
|
||||
id="alamat"
|
||||
<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>
|
||||
|
||||
|
|
@ -240,108 +196,69 @@ class="input-field w-full border border-gray-200 rounded-xl px-3 py-2 text-sm fo
|
|||
<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"
|
||||
<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 --}}
|
||||
value="{{ $myDusunName }}" readonly>
|
||||
<input type="hidden" name="desa" id="desa" value="{{ $myDusunName }}">
|
||||
|
||||
<p class="error-msg text-xs text-red-500 mt-1 hidden">Dusun wajib diisi.</p>
|
||||
</div>
|
||||
|
||||
{{-- 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"
|
||||
<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>
|
||||
|
||||
{{-- SECTION 3: Data Ekonomi --}}
|
||||
<div class="bg-white rounded-2xl shadow-sm border border-gray-100 mb-5 overflow-hidden">
|
||||
<div class="px-5 py-3 border-b border-gray-100 flex items-center gap-2 bg-gray-50">
|
||||
<div class="bg-white rounded-2xl shadow-sm border border-gray-100 mb-4 overflow-hidden">
|
||||
<div class="section-head 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-1 md:grid-cols-2 lg:grid-cols-4 gap-4">
|
||||
{{-- PEKERJAAN --}}
|
||||
<div class="section-body p-5 grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-4">
|
||||
<div class="field-group">
|
||||
<label class="block text-xs font-semibold text-gray-600 mb-1">
|
||||
Pekerjaan <span class="text-red-500">*</span>
|
||||
</label>
|
||||
<input
|
||||
type="text"
|
||||
name="pekerjaan"
|
||||
id="pekerjaan"
|
||||
<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"
|
||||
<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 inputmode="decimal">
|
||||
<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"
|
||||
<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 inputmode="numeric">
|
||||
<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"
|
||||
>
|
||||
<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>
|
||||
|
|
@ -349,18 +266,112 @@ class="input-field w-full border border-gray-200 rounded-xl px-3 py-2 text-sm fo
|
|||
</div>
|
||||
</div>
|
||||
|
||||
{{-- SECTION 4: Kondisi Tempat Tinggal --}}
|
||||
<div class="bg-white rounded-2xl shadow-sm border border-gray-100 mb-4 overflow-hidden">
|
||||
<div class="section-head px-5 py-3 border-b border-gray-100 flex items-center gap-2 bg-gray-50">
|
||||
<div class="w-1 h-4 bg-purple-500 rounded-full"></div>
|
||||
<h3 class="text-sm font-semibold text-gray-700">Kondisi Tempat Tinggal</h3>
|
||||
</div>
|
||||
<div class="section-body p-5 grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
|
||||
<div class="field-group">
|
||||
<label class="block text-xs font-semibold text-gray-600 mb-1">
|
||||
Kondisi Rumah <span class="text-red-500">*</span>
|
||||
</label>
|
||||
<select name="kondisi_rumah" id="kondisi_rumah"
|
||||
class="input-field w-full border border-gray-200 rounded-xl px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent bg-gray-50">
|
||||
<option value="Layak">Layak</option>
|
||||
<option value="Sedang">Sedang</option>
|
||||
<option value="Tidak Layak">Tidak Layak</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="field-group">
|
||||
<label class="block text-xs font-semibold text-gray-600 mb-1">
|
||||
Meteran Listrik <span class="text-red-500">*</span>
|
||||
</label>
|
||||
<select name="meteran_listrik" id="meteran_listrik"
|
||||
class="input-field w-full border border-gray-200 rounded-xl px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent bg-gray-50">
|
||||
<option value="450VA">450VA</option>
|
||||
<option value="900VA">900VA</option>
|
||||
<option value="1300VA+">1300VA+</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="field-group">
|
||||
<label class="block text-xs font-semibold text-gray-600 mb-1">
|
||||
Sumber Air <span class="text-red-500">*</span>
|
||||
</label>
|
||||
<select name="sumber_air" id="sumber_air"
|
||||
class="input-field w-full border border-gray-200 rounded-xl px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent bg-gray-50">
|
||||
<option value="PDAM">PDAM</option>
|
||||
<option value="Sumur">Sumur</option>
|
||||
<option value="Sungai">Sungai</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{-- SECTION 5: Upload Dokumen & Foto --}}
|
||||
<div class="bg-white rounded-2xl shadow-sm border border-gray-100 mb-5 overflow-hidden">
|
||||
<div class="section-head px-5 py-3 border-b border-gray-100 flex items-center gap-2 bg-gray-50">
|
||||
<div class="w-1 h-4 bg-rose-500 rounded-full"></div>
|
||||
<h3 class="text-sm font-semibold text-gray-700">Upload Dokumen & Foto</h3>
|
||||
</div>
|
||||
<div class="section-body p-5 grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-4">
|
||||
<div class="field-group">
|
||||
<label class="block text-xs font-semibold text-gray-600 mb-1">Foto Rumah Depan</label>
|
||||
<input type="file" name="foto_rumah_depan" accept="image/*"
|
||||
class="input-field w-full border border-gray-200 rounded-xl px-3 py-2 text-sm bg-gray-50">
|
||||
</div>
|
||||
<div class="field-group">
|
||||
<label class="block text-xs font-semibold text-gray-600 mb-1">Foto Rumah Belakang</label>
|
||||
<input type="file" name="foto_rumah_belakang" accept="image/*"
|
||||
class="input-field w-full border border-gray-200 rounded-xl px-3 py-2 text-sm bg-gray-50">
|
||||
</div>
|
||||
<div class="field-group">
|
||||
<label class="block text-xs font-semibold text-gray-600 mb-1">Foto Rumah Kanan</label>
|
||||
<input type="file" name="foto_rumah_kanan" accept="image/*"
|
||||
class="input-field w-full border border-gray-200 rounded-xl px-3 py-2 text-sm bg-gray-50">
|
||||
</div>
|
||||
<div class="field-group">
|
||||
<label class="block text-xs font-semibold text-gray-600 mb-1">Foto Rumah Kiri</label>
|
||||
<input type="file" name="foto_rumah_kiri" accept="image/*"
|
||||
class="input-field w-full border border-gray-200 rounded-xl px-3 py-2 text-sm bg-gray-50">
|
||||
</div>
|
||||
<div class="field-group">
|
||||
<label class="block text-xs font-semibold text-gray-600 mb-1">Foto KK</label>
|
||||
<input type="file" name="foto_kk" accept="image/*"
|
||||
class="input-field w-full border border-gray-200 rounded-xl px-3 py-2 text-sm bg-gray-50">
|
||||
</div>
|
||||
<div class="field-group">
|
||||
<label class="block text-xs font-semibold text-gray-600 mb-1">Foto KTP</label>
|
||||
<input type="file" name="foto_ktp" accept="image/*"
|
||||
class="input-field w-full border border-gray-200 rounded-xl px-3 py-2 text-sm bg-gray-50">
|
||||
</div>
|
||||
<div class="field-group">
|
||||
<label class="block text-xs font-semibold text-gray-600 mb-1">Foto Rekening Listrik</label>
|
||||
<input type="file" name="foto_rekening_listrik" accept="image/*,.pdf"
|
||||
class="input-field w-full border border-gray-200 rounded-xl px-3 py-2 text-sm bg-gray-50">
|
||||
</div>
|
||||
<div class="field-group">
|
||||
<label class="block text-xs font-semibold text-gray-600 mb-1">Foto Meteran Air</label>
|
||||
<input type="file" name="foto_meteran_air" accept="image/*"
|
||||
class="input-field w-full border border-gray-200 rounded-xl px-3 py-2 text-sm bg-gray-50">
|
||||
</div>
|
||||
<div class="field-group lg:col-span-2">
|
||||
<label class="block text-xs font-semibold text-gray-600 mb-1">Dokumen Pendukung Lainnya</label>
|
||||
<input type="file" name="dokumen_pendukung" accept="image/*,.pdf,.doc,.docx"
|
||||
class="input-field w-full border border-gray-200 rounded-xl px-3 py-2 text-sm bg-gray-50">
|
||||
</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"
|
||||
>
|
||||
<div class="action-row 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">
|
||||
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>
|
||||
|
|
@ -431,7 +442,6 @@ function showToast(msg) {
|
|||
});
|
||||
});
|
||||
|
||||
// Auto-isi usia dari tanggal lahir
|
||||
document.getElementById('tanggal_lahir')?.addEventListener('change', function () {
|
||||
const dob = new Date(this.value);
|
||||
const today = new Date();
|
||||
|
|
@ -444,7 +454,6 @@ function showToast(msg) {
|
|||
}
|
||||
});
|
||||
|
||||
// Submit validation
|
||||
document.getElementById('formPendataan').addEventListener('submit', function (e) {
|
||||
let valid = true;
|
||||
Object.keys(rules).forEach(id => {
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
<x-app-layout>
|
||||
<x-slot name="header">
|
||||
<div class="flex items-center justify-between">
|
||||
<div class="flex items-center justify-between flex-wrap gap-3">
|
||||
<div>
|
||||
<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>
|
||||
|
|
@ -15,6 +15,47 @@ class="inline-flex items-center gap-1.5 px-4 py-2 bg-white border border-gray-20
|
|||
</div>
|
||||
</x-slot>
|
||||
|
||||
<style>
|
||||
/* Cegah auto-zoom iOS */
|
||||
.input-field { font-size: 16px !important; }
|
||||
@media (min-width: 640px) { .input-field { font-size: 14px !important; } }
|
||||
|
||||
/* Grid section: 1 kolom di mobile, 2 di md, 4 di lg */
|
||||
.section-grid {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr;
|
||||
gap: 14px;
|
||||
padding: 16px;
|
||||
}
|
||||
@media (min-width: 640px) {
|
||||
.section-grid { grid-template-columns: 1fr 1fr; padding: 18px; gap: 16px; }
|
||||
}
|
||||
@media (min-width: 1024px) {
|
||||
.section-grid { grid-template-columns: repeat(4, 1fr); padding: 20px; }
|
||||
}
|
||||
|
||||
/* span 2 kolom hanya ketika grid sudah 2+ kolom */
|
||||
.col-span-2-up { grid-column: span 1; }
|
||||
@media (min-width: 640px) { .col-span-2-up { grid-column: span 2; } }
|
||||
|
||||
/* Padding section card lebih kecil di mobile */
|
||||
.section-head { padding: 10px 16px; }
|
||||
@media (min-width: 640px) { .section-head { padding: 10px 20px; } }
|
||||
|
||||
/* Tombol aksi full-width di mobile */
|
||||
.action-row {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
gap: 10px;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
@media (max-width: 480px) {
|
||||
.action-row { flex-direction: column-reverse; }
|
||||
.action-row a,
|
||||
.action-row button { width: 100%; justify-content: center; text-align: center; }
|
||||
}
|
||||
</style>
|
||||
|
||||
{{-- ERROR VALIDASI --}}
|
||||
@if ($errors->any())
|
||||
<div class="mb-4 flex items-start gap-3 rounded-xl border border-red-200 bg-red-50 px-4 py-3 text-sm text-red-700">
|
||||
|
|
@ -35,103 +76,93 @@ class="inline-flex items-center gap-1.5 px-4 py-2 bg-white border border-gray-20
|
|||
<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">
|
||||
<div class="section-head 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>
|
||||
<div class="p-5 grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-4">
|
||||
<div class="section-grid">
|
||||
|
||||
{{-- RT (readonly) --}}
|
||||
<div class="field-group">
|
||||
<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"
|
||||
class="w-full border border-gray-200 rounded-xl px-3 py-2 bg-gray-100 text-gray-700 cursor-not-allowed"
|
||||
style="font-size:16px;"
|
||||
readonly>
|
||||
<p class="text-[11px] text-gray-400 mt-1">RT mengikuti akun RT yang sedang login.</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"
|
||||
value="{{ old('no_kk', $calonPenerima->no_kk) }}"
|
||||
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>
|
||||
class="input-field w-full border border-gray-200 rounded-xl px-3 py-2 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent bg-gray-50"
|
||||
placeholder="16 digit No. KK" required inputmode="numeric">
|
||||
<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"
|
||||
value="{{ old('nik', $calonPenerima->nik) }}"
|
||||
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>
|
||||
class="input-field w-full border border-gray-200 rounded-xl px-3 py-2 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent bg-gray-50"
|
||||
placeholder="16 digit NIK" required inputmode="numeric">
|
||||
<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"
|
||||
value="{{ old('nama_lengkap', $calonPenerima->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"
|
||||
class="input-field w-full border border-gray-200 rounded-xl px-3 py-2 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent bg-gray-50"
|
||||
placeholder="Nama sesuai KTP" required>
|
||||
<p class="error-msg text-xs text-red-500 mt-1 hidden">Nama minimal 3 karakter.</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">
|
||||
class="input-field w-full border border-gray-200 rounded-xl px-3 py-2 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent bg-gray-50">
|
||||
<option value="Laki-laki" {{ old('jenis_kelamin', $calonPenerima->jenis_kelamin) === 'Laki-laki' ? 'selected' : '' }}>Laki-laki</option>
|
||||
<option value="Perempuan" {{ old('jenis_kelamin', $calonPenerima->jenis_kelamin) === 'Perempuan' ? 'selected' : '' }}>Perempuan</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
{{-- 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"
|
||||
value="{{ old('tempat_lahir', $calonPenerima->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"
|
||||
class="input-field w-full border border-gray-200 rounded-xl px-3 py-2 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent bg-gray-50"
|
||||
placeholder="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"
|
||||
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"
|
||||
class="input-field w-full border border-gray-200 rounded-xl px-3 py-2 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent bg-gray-50"
|
||||
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="17" max="100"
|
||||
value="{{ old('usia', $calonPenerima->usia) }}"
|
||||
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="Tahun" required>
|
||||
class="input-field w-full border border-gray-200 rounded-xl px-3 py-2 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent bg-gray-50"
|
||||
placeholder="Tahun" required inputmode="numeric">
|
||||
<p class="error-msg text-xs text-red-500 mt-1 hidden">Usia harus antara 17–100 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">
|
||||
class="input-field w-full border border-gray-200 rounded-xl px-3 py-2 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent bg-gray-50">
|
||||
<option value="">-- Pilih --</option>
|
||||
@foreach(['Belum Kawin','Kawin','Cerai Hidup','Cerai Mati'] as $sp)
|
||||
<option value="{{ $sp }}" {{ old('status_perkawinan', $calonPenerima->status_perkawinan) === $sp ? 'selected' : '' }}>{{ $sp }}</option>
|
||||
|
|
@ -145,27 +176,27 @@ class="input-field w-full border border-gray-200 rounded-xl px-3 py-2 text-sm fo
|
|||
|
||||
{{-- SECTION 2: Data Tempat Tinggal --}}
|
||||
<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">
|
||||
<div class="section-head 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-1 md:grid-cols-2 lg:grid-cols-4 gap-4">
|
||||
<div class="section-grid">
|
||||
|
||||
<div class="field-group lg:col-span-2">
|
||||
<div class="field-group col-span-2-up">
|
||||
<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"
|
||||
value="{{ old('alamat', $calonPenerima->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"
|
||||
class="input-field w-full border border-gray-200 rounded-xl px-3 py-2 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent bg-gray-50"
|
||||
placeholder="Jalan, nomor rumah, RT/RW" required>
|
||||
<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</label>
|
||||
<input type="text" name="desa" id="desa"
|
||||
value="{{ old('desa', $calonPenerima->desa) }}"
|
||||
class="w-full border border-gray-200 rounded-xl px-3 py-2 text-sm bg-gray-100 text-gray-700 cursor-not-allowed"
|
||||
class="w-full border border-gray-200 rounded-xl px-3 py-2 bg-gray-100 text-gray-700 cursor-not-allowed"
|
||||
style="font-size:16px;"
|
||||
readonly>
|
||||
<p class="text-[11px] text-gray-400 mt-1">Dusun mengikuti RT dan tidak dapat diubah.</p>
|
||||
</div>
|
||||
|
|
@ -174,7 +205,7 @@ class="w-full border border-gray-200 rounded-xl px-3 py-2 text-sm bg-gray-100 te
|
|||
<label class="block text-xs font-semibold text-gray-600 mb-1">Aset Kepemilikan <span class="text-red-500">*</span></label>
|
||||
<input type="text" name="aset_kepemilikan" id="aset_kepemilikan"
|
||||
value="{{ old('aset_kepemilikan', $calonPenerima->aset_kepemilikan) }}"
|
||||
class="input-field w-full border border-gray-200 rounded-xl px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent bg-gray-50"
|
||||
class="input-field w-full border border-gray-200 rounded-xl px-3 py-2 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent bg-gray-50"
|
||||
placeholder="Misal: Rumah, Motor, dll" required>
|
||||
<p class="error-msg text-xs text-red-500 mt-1 hidden">Aset kepemilikan wajib diisi.</p>
|
||||
</div>
|
||||
|
|
@ -184,17 +215,17 @@ class="input-field w-full border border-gray-200 rounded-xl px-3 py-2 text-sm fo
|
|||
|
||||
{{-- SECTION 3: Data Ekonomi --}}
|
||||
<div class="bg-white rounded-2xl shadow-sm border border-gray-100 mb-5 overflow-hidden">
|
||||
<div class="px-5 py-3 border-b border-gray-100 flex items-center gap-2 bg-gray-50">
|
||||
<div class="section-head border-b border-gray-100 flex items-center gap-2 bg-gray-50">
|
||||
<div class="w-1 h-4 bg-amber-500 rounded-full"></div>
|
||||
<h3 class="text-sm font-semibold text-gray-700">Data Ekonomi</h3>
|
||||
</div>
|
||||
<div class="p-5 grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-4">
|
||||
<div class="section-grid">
|
||||
|
||||
<div class="field-group">
|
||||
<label class="block text-xs font-semibold text-gray-600 mb-1">Pekerjaan <span class="text-red-500">*</span></label>
|
||||
<input type="text" name="pekerjaan" id="pekerjaan"
|
||||
value="{{ old('pekerjaan', $calonPenerima->pekerjaan) }}"
|
||||
class="input-field w-full border border-gray-200 rounded-xl px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent bg-gray-50"
|
||||
class="input-field w-full border border-gray-200 rounded-xl px-3 py-2 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent bg-gray-50"
|
||||
placeholder="Jenis pekerjaan" required>
|
||||
<p class="error-msg text-xs text-red-500 mt-1 hidden">Pekerjaan wajib diisi.</p>
|
||||
</div>
|
||||
|
|
@ -203,8 +234,8 @@ class="input-field w-full border border-gray-200 rounded-xl px-3 py-2 text-sm fo
|
|||
<label class="block text-xs font-semibold text-gray-600 mb-1">Penghasilan (Rp) <span class="text-red-500">*</span></label>
|
||||
<input type="number" step="0.01" name="penghasilan" id="penghasilan" min="0"
|
||||
value="{{ old('penghasilan', $calonPenerima->penghasilan) }}"
|
||||
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>
|
||||
class="input-field w-full border border-gray-200 rounded-xl px-3 py-2 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent bg-gray-50"
|
||||
placeholder="0" required inputmode="decimal">
|
||||
<p class="error-msg text-xs text-red-500 mt-1 hidden">Penghasilan tidak boleh negatif.</p>
|
||||
</div>
|
||||
|
||||
|
|
@ -212,15 +243,15 @@ class="input-field w-full border border-gray-200 rounded-xl px-3 py-2 text-sm fo
|
|||
<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"
|
||||
value="{{ old('jumlah_tanggungan', $calonPenerima->jumlah_tanggungan) }}"
|
||||
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>
|
||||
class="input-field w-full border border-gray-200 rounded-xl px-3 py-2 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent bg-gray-50"
|
||||
placeholder="Jumlah orang" required inputmode="numeric">
|
||||
<p class="error-msg text-xs text-red-500 mt-1 hidden">Jumlah tanggungan tidak boleh negatif.</p>
|
||||
</div>
|
||||
|
||||
<div class="field-group">
|
||||
<label class="block text-xs font-semibold text-gray-600 mb-1">Menerima Bantuan Lain? <span class="text-red-500">*</span></label>
|
||||
<select name="bantuan_lain"
|
||||
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">
|
||||
class="input-field w-full border border-gray-200 rounded-xl px-3 py-2 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent bg-gray-50">
|
||||
<option value="tidak" {{ old('bantuan_lain', $calonPenerima->bantuan_lain) === 'tidak' ? 'selected' : '' }}>Tidak</option>
|
||||
<option value="ya" {{ old('bantuan_lain', $calonPenerima->bantuan_lain) === 'ya' ? 'selected' : '' }}>Ya</option>
|
||||
</select>
|
||||
|
|
@ -230,9 +261,9 @@ class="input-field w-full border border-gray-200 rounded-xl px-3 py-2 text-sm fo
|
|||
</div>
|
||||
|
||||
{{-- ACTIONS --}}
|
||||
<div class="flex justify-end gap-3">
|
||||
<div class="action-row">
|
||||
<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">
|
||||
class="inline-flex items-center gap-1.5 px-5 py-2 text-sm font-medium bg-white border border-gray-200 text-gray-600 rounded-xl hover:bg-gray-50 transition">
|
||||
Batal
|
||||
</a>
|
||||
<button type="submit"
|
||||
|
|
@ -247,7 +278,7 @@ class="inline-flex items-center gap-2 px-6 py-2 text-sm font-semibold bg-blue-60
|
|||
</form>
|
||||
|
||||
{{-- TOAST --}}
|
||||
<div id="toast" class="fixed bottom-5 right-5 z-50 hidden">
|
||||
<div id="toast" class="fixed bottom-5 right-5 z-50 hidden" style="max-width:calc(100vw - 40px);">
|
||||
<div class="flex items-center gap-3 bg-red-500 text-white text-sm font-medium px-4 py-3 rounded-xl shadow-lg">
|
||||
<svg class="w-4 h-4 shrink-0" 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"/>
|
||||
|
|
@ -263,14 +294,12 @@ 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;
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
<x-app-layout>
|
||||
<x-slot name="header">
|
||||
<div style="display:flex;align-items:center;justify-content:space-between;gap:16px;">
|
||||
<div style="display:flex;align-items:center;justify-content:space-between;gap:16px;flex-wrap:wrap;">
|
||||
<div>
|
||||
<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>
|
||||
|
|
@ -23,14 +23,29 @@
|
|||
/* 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;}
|
||||
/* ── Desktop Table ── */
|
||||
.tbl-wrap{overflow-x:auto;-webkit-overflow-scrolling:touch;}
|
||||
table.idx-tbl{width:100%;border-collapse:collapse;min-width:680px;}
|
||||
table.idx-tbl thead tr{background:#f8fafc;border-bottom:1.5px solid #f1f5f9;}
|
||||
table.idx-tbl 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;}
|
||||
table.idx-tbl tbody tr{border-bottom:1px solid #f8fafc;transition:background .12s;}
|
||||
table.idx-tbl tbody tr:hover{background:#f0f7ff;}
|
||||
table.idx-tbl tbody tr:last-child{border-bottom:none;}
|
||||
table.idx-tbl tbody td{padding:11px 14px;vertical-align:middle;}
|
||||
|
||||
/* ── Mobile Card List ── */
|
||||
.mob-list{display:none;flex-direction:column;gap:10px;padding:14px;}
|
||||
@media(max-width:640px){
|
||||
.tbl-wrap{display:none;}
|
||||
.mob-list{display:flex;}
|
||||
}
|
||||
.mob-card{background:#fafbfc;border:1.5px solid #f1f5f9;border-radius:14px;padding:13px;}
|
||||
.mob-top{display:flex;align-items:center;gap:10px;margin-bottom:10px;}
|
||||
.mob-name{font-size:13px;font-weight:800;color:#111827;flex:1;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;}
|
||||
.mob-sub{font-size:10.5px;color:#94a3b8;margin-top:1px;font-family:monospace;}
|
||||
.mob-row{display:flex;justify-content:space-between;align-items:center;gap:8px;flex-wrap:wrap;margin-bottom:10px;}
|
||||
.mob-actions{display:flex;gap:6px;flex-wrap:wrap;padding-top:10px;border-top:1px solid #f1f5f9;}
|
||||
.mob-actions .btn-act{flex:1;justify-content:center;}
|
||||
|
||||
/* Avatar */
|
||||
.av{width:32px;height:32px;border-radius:10px;display:flex;align-items:center;justify-content:center;font-size:12px;font-weight:800;color:#fff;flex-shrink:0;background:linear-gradient(135deg,#3b82f6,#2563eb);}
|
||||
|
|
@ -40,19 +55,16 @@
|
|||
.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{display:inline-flex;align-items:center;gap:4px;padding:5px 10px;border-radius:8px;font-size:11px;font-weight:600;text-decoration:none;border:none;cursor:pointer;white-space:nowrap;line-height:1.4;}
|
||||
.btn-act svg{width:11px;height:11px;flex-shrink:0;}
|
||||
.btn-detail{background:#f3f4f6;color:#374151;} .btn-detail:hover{background:#e5e7eb;}
|
||||
.btn-edit {background:#eff6ff;color:#1d4ed8;} .btn-edit:hover{background:#dbeafe;}
|
||||
|
|
@ -64,7 +76,7 @@
|
|||
.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 */
|
||||
/* Pagination */
|
||||
.pagi{padding:10px 16px;background:#fafafa;border-top:1px solid #f1f5f9;}
|
||||
</style>
|
||||
|
||||
|
|
@ -85,7 +97,7 @@
|
|||
<div class="idx-card">
|
||||
|
||||
{{-- HEADER BAR --}}
|
||||
<div style="padding:10px 16px;border-bottom:1px solid #f1f5f9;background:#fafafa;display:flex;align-items:center;justify-content:space-between;gap:12px;">
|
||||
<div style="padding:10px 16px;border-bottom:1px solid #f1f5f9;background:#fafafa;display:flex;align-items:center;justify-content:space-between;gap:12px;flex-wrap:wrap;">
|
||||
<div style="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;">
|
||||
|
|
@ -97,9 +109,9 @@
|
|||
</span>
|
||||
</div>
|
||||
|
||||
{{-- TABLE --}}
|
||||
<div style="overflow-x:auto;">
|
||||
<table>
|
||||
{{-- ── Desktop: Tabel ── --}}
|
||||
<div class="tbl-wrap">
|
||||
<table class="idx-tbl">
|
||||
<thead>
|
||||
<tr>
|
||||
<th style="width:40px;">#</th>
|
||||
|
|
@ -114,38 +126,21 @@
|
|||
</thead>
|
||||
<tbody>
|
||||
@forelse($calonPenerimas as $index => $item)
|
||||
@php $trk = $item->tracking_status ?? 'draft'; @endphp
|
||||
<tr>
|
||||
{{-- No --}}
|
||||
<td style="font-size:11px;color:#9ca3af;font-weight:600;text-align:center;">
|
||||
{{ $calonPenerimas->firstItem() + $index }}
|
||||
</td>
|
||||
|
||||
{{-- 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 style="font-family:monospace;font-size:11.5px;color:#6b7280;letter-spacing:.02em;">
|
||||
{{ $item->nik }}
|
||||
</td>
|
||||
|
||||
{{-- RT --}}
|
||||
<td style="font-family:monospace;font-size:11.5px;color:#6b7280;letter-spacing:.02em;">{{ $item->nik }}</td>
|
||||
<td><span class="badge-rt">RT {{ str_pad($item->rt->nomor_rt ?? '-', 3, '0', STR_PAD_LEFT) }}</span></td>
|
||||
<td style="font-size:12px;color:#6b7280;">{{ $item->rt->dusun->nama_dusun ?? '-' }}</td>
|
||||
<td>
|
||||
<span class="badge-rt">RT {{ str_pad($item->rt->nomor_rt ?? '-', 3, '0', STR_PAD_LEFT) }}</span>
|
||||
</td>
|
||||
|
||||
{{-- Dusun --}}
|
||||
<td style="font-size:12px;color:#6b7280;">
|
||||
{{ $item->rt->dusun->nama_dusun ?? '-' }}
|
||||
</td>
|
||||
|
||||
{{-- 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')
|
||||
|
|
@ -158,8 +153,6 @@
|
|||
<span class="badge trk-draft"><span class="dot"></span>—</span>
|
||||
@endif
|
||||
</td>
|
||||
|
||||
{{-- Status --}}
|
||||
<td>
|
||||
@if($item->status_verifikasi === 'pending')
|
||||
<span class="badge st-pending"><span class="dot"></span>Pending</span>
|
||||
|
|
@ -169,21 +162,17 @@
|
|||
<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($trk === 'draft')
|
||||
<a href="{{ route('rt.calon-penerima.edit', $item->id) }}" class="btn-act btn-edit">
|
||||
<svg fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M11 5H6a2 2 0 00-2 2v11a2 2 0 002 2h11a2 2 0 002-2v-5m-1.414-9.414a2 2 0 112.828 2.828L11.828 15H9v-2.828l8.586-8.586z"/></svg>
|
||||
Edit
|
||||
</a>
|
||||
|
||||
<form method="POST" action="{{ route('rt.calon-penerima.ajukan', $item->id) }}"
|
||||
onsubmit="return confirm('Yakin ajukan data {{ addslashes($item->nama_lengkap) }}? Setelah diajukan data tidak bisa diubah.')">
|
||||
@csrf @method('PATCH')
|
||||
|
|
@ -192,7 +181,6 @@
|
|||
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')
|
||||
|
|
@ -222,7 +210,85 @@
|
|||
</table>
|
||||
</div>
|
||||
|
||||
{{-- PAGINATION --}}
|
||||
{{-- ── Mobile: Card List (< 640px) ── --}}
|
||||
<div class="mob-list">
|
||||
@forelse($calonPenerimas as $index => $item)
|
||||
@php $trk = $item->tracking_status ?? 'draft'; @endphp
|
||||
<div class="mob-card">
|
||||
<div class="mob-top">
|
||||
<div class="av">{{ strtoupper(substr($item->nama_lengkap, 0, 1)) }}</div>
|
||||
<div style="flex:1;min-width:0;">
|
||||
<div class="mob-name">{{ $item->nama_lengkap }}</div>
|
||||
<div class="mob-sub">{{ $item->nik }}</div>
|
||||
</div>
|
||||
<span style="font-size:10px;color:#94a3b8;font-weight:700;">
|
||||
#{{ $calonPenerimas->firstItem() + $index }}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div class="mob-row">
|
||||
<span class="badge-rt">RT {{ str_pad($item->rt->nomor_rt ?? '-', 3, '0', STR_PAD_LEFT) }}</span>
|
||||
<span style="font-size:11px;color:#6b7280;">{{ $item->rt->dusun->nama_dusun ?? '-' }}</span>
|
||||
@if($trk === 'draft')
|
||||
<span class="badge trk-draft"><span class="dot"></span>Draft</span>
|
||||
@elseif($trk === 'terkirim')
|
||||
<span class="badge trk-terkirim"><span class="dot"></span>Terkirim</span>
|
||||
@elseif($trk === 'sedang_validasi')
|
||||
<span class="badge trk-validasi"><span class="dot"></span>Validasi</span>
|
||||
@elseif($trk === 'selesai')
|
||||
<span class="badge trk-selesai"><span class="dot"></span>Selesai</span>
|
||||
@endif
|
||||
@if($item->status_verifikasi === 'pending')
|
||||
<span class="badge st-pending"><span class="dot"></span>Pending</span>
|
||||
@elseif($item->status_verifikasi === 'disetujui')
|
||||
<span class="badge st-disetujui"><span class="dot"></span>Disetujui</span>
|
||||
@else
|
||||
<span class="badge st-ditolak"><span class="dot"></span>Ditolak</span>
|
||||
@endif
|
||||
</div>
|
||||
|
||||
<div class="mob-actions">
|
||||
<a href="{{ route('rt.calon-penerima.show', $item->id) }}" class="btn-act btn-detail">
|
||||
<svg fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 12a3 3 0 11-6 0 3 3 0 016 0z"/><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M2.458 12C3.732 7.943 7.523 5 12 5c4.478 0 8.268 2.943 9.542 7-1.274 4.057-5.064 7-9.542 7-4.477 0-8.268-2.943-9.542-7z"/></svg>
|
||||
Detail
|
||||
</a>
|
||||
@if($trk === 'draft')
|
||||
<a href="{{ route('rt.calon-penerima.edit', $item->id) }}" class="btn-act btn-edit">
|
||||
<svg fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M11 5H6a2 2 0 00-2 2v11a2 2 0 002 2h11a2 2 0 002-2v-5m-1.414-9.414a2 2 0 112.828 2.828L11.828 15H9v-2.828l8.586-8.586z"/></svg>
|
||||
Edit
|
||||
</a>
|
||||
<form method="POST" action="{{ route('rt.calon-penerima.ajukan', $item->id) }}"
|
||||
onsubmit="return confirm('Yakin ajukan data {{ addslashes($item->nama_lengkap) }}?')"
|
||||
style="display:contents;">
|
||||
@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) }}?')"
|
||||
style="display:contents;">
|
||||
@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>
|
||||
</div>
|
||||
@empty
|
||||
<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 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>
|
||||
@endforelse
|
||||
</div>
|
||||
|
||||
<div class="pagi">
|
||||
{{ $calonPenerimas->links() }}
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -1,21 +1,21 @@
|
|||
<x-app-layout>
|
||||
<x-slot name="header">
|
||||
<div style="display:flex; align-items:center; justify-content:space-between; gap:16px;">
|
||||
<div style="display:flex; align-items:center; gap:12px;">
|
||||
<div style="display:flex; align-items:center; justify-content:space-between; gap:12px; flex-wrap:wrap;">
|
||||
<div style="display:flex; align-items:center; gap:10px; min-width:0;">
|
||||
<a href="{{ route('rt.calon-penerima.index') }}"
|
||||
style="width:32px; height:32px; display:flex; align-items:center; justify-content:center; border-radius:10px; background:#fff; border:1.5px solid #e5e7eb; color:#6b7280; text-decoration:none;">
|
||||
style="width:32px; height:32px; display:flex; align-items:center; justify-content:center; border-radius:10px; background:#fff; border:1.5px solid #e5e7eb; color:#6b7280; text-decoration:none; flex-shrink:0;">
|
||||
<svg width="14" height="14" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10 19l-7-7m0 0l7-7m-7 7h18"/>
|
||||
</svg>
|
||||
</a>
|
||||
<div>
|
||||
<h2 style="font-size:17px; font-weight:800; color:#111827; line-height:1.2;">Detail Calon Penerima</h2>
|
||||
<div style="min-width:0;">
|
||||
<h2 style="font-size:16px; font-weight:800; color:#111827; line-height:1.2; white-space:nowrap; overflow:hidden; text-overflow:ellipsis;">Detail Calon Penerima</h2>
|
||||
<p style="font-size:11px; color:#9ca3af; margin-top:2px;">Data pendataan warga & hasil prediksi kelayakan</p>
|
||||
</div>
|
||||
</div>
|
||||
@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);">
|
||||
style="display:inline-flex; align-items:center; gap:6px; padding:7px 14px; background:#2563eb; color:#fff; font-size:12px; font-weight:700; border-radius:10px; text-decoration:none; box-shadow:0 2px 8px rgba(37,99,235,.25); flex-shrink:0;">
|
||||
<svg width="13" height="13" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M11 5H6a2 2 0 00-2 2v11a2 2 0 002 2h11a2 2 0 002-2v-5m-1.414-9.414a2 2 0 112.828 2.828L11.828 15H9v-2.828l8.586-8.586z"/>
|
||||
</svg>
|
||||
|
|
@ -26,31 +26,96 @@
|
|||
</x-slot>
|
||||
|
||||
<style>
|
||||
/* ── CARDS ── */
|
||||
.sp-card{background:#fff;border-radius:16px;border:1.5px solid #f1f5f9;box-shadow:0 1px 3px rgba(0,0,0,.04);overflow:hidden;}
|
||||
.sp-head{padding:9px 16px;border-bottom:1px solid #f1f5f9;background:#fafafa;display:flex;align-items:center;gap:8px;}
|
||||
.sp-head .bar{width:3px;height:14px;border-radius:4px;flex-shrink:0;}
|
||||
.sp-head h3{font-size:10.5px;font-weight:700;color:#6b7280;text-transform:uppercase;letter-spacing:.07em;}
|
||||
.sp-body{padding:13px 16px;}
|
||||
|
||||
/* ── LABELS / VALUES ── */
|
||||
.lbl{font-size:10px;color:#9ca3af;text-transform:uppercase;letter-spacing:.07em;margin-bottom:2px;}
|
||||
.val{font-size:12.5px;font-weight:600;color:#111827;}
|
||||
|
||||
/* ── GRIDS ── */
|
||||
.g2{display:grid;grid-template-columns:1fr 1fr;gap:12px;}
|
||||
.g3{display:grid;grid-template-columns:repeat(3,1fr);gap:12px;}
|
||||
.g4{display:grid;grid-template-columns:repeat(4,1fr);gap:12px;}
|
||||
|
||||
/* ── PILLS ── */
|
||||
.pill{display:inline-flex;align-items:center;gap:5px;padding:4px 11px;border-radius:20px;font-size:11px;font-weight:700;}
|
||||
.dot{width:6px;height:6px;border-radius:50%;flex-shrink:0;}
|
||||
|
||||
/* ── FEATURE LIST ── */
|
||||
.fl{list-style:none;padding:0;margin:0;}
|
||||
.fl li{display:flex;align-items:flex-start;gap:5px;font-size:11.5px;line-height:1.5;margin-bottom:2px;}
|
||||
.fi{width:13px;height:13px;flex-shrink:0;margin-top:1px;}
|
||||
|
||||
/* ── ACCORDION ── */
|
||||
details.acc>summary{list-style:none;display:flex;justify-content:space-between;align-items:center;padding:9px 16px;cursor:pointer;background:#f9fafb;border-top:1px solid #f1f5f9;font-size:11px;font-weight:700;color:#374151;user-select:none;}
|
||||
details.acc>summary::-webkit-details-marker{display:none;}
|
||||
details.acc>summary .chev{transition:transform .2s;}
|
||||
details.acc[open]>summary .chev{transform:rotate(180deg);}
|
||||
details.acc .ab{padding:12px 16px;display:flex;flex-direction:column;gap:9px;}
|
||||
|
||||
/* ── TRACKING ── */
|
||||
.trk-wrap{display:flex;flex-direction:column;gap:10px;}
|
||||
.trk-step{display:flex;align-items:flex-start;gap:10px;}
|
||||
.trk-point{width:22px;height:22px;border-radius:50%;display:flex;align-items:center;justify-content:center;flex-shrink:0;font-size:11px;font-weight:800;}
|
||||
.trk-line{width:2px;height:18px;margin-left:10px;border-radius:999px;background:#e5e7eb;}
|
||||
@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;}}
|
||||
|
||||
/* ── MAIN GRID (2-col di desktop, 1-col di tablet/mobile) ── */
|
||||
.mg{display:grid;grid-template-columns:1fr 310px;gap:12px;align-items:start;}
|
||||
@media(max-width:1024px){
|
||||
.mg{grid-template-columns:1fr;}
|
||||
}
|
||||
|
||||
/* ── INNER GRIDS ── */
|
||||
@media(max-width:860px){
|
||||
.g4{grid-template-columns:1fr 1fr;}
|
||||
.g3{grid-template-columns:1fr 1fr;}
|
||||
}
|
||||
/* Di bawah 540px: semua jadi 1 kolom kecuali .g2 tetap 2 */
|
||||
@media(max-width:540px){
|
||||
.g3,.g4{grid-template-columns:1fr;}
|
||||
}
|
||||
@media(max-width:420px){
|
||||
.g2{grid-template-columns:1fr;}
|
||||
}
|
||||
|
||||
/* ── BANNER ── */
|
||||
.bnr-inner{display:flex;align-items:center;gap:14px;flex-wrap:nowrap;}
|
||||
.bnr-mid{flex:1;min-width:0;}
|
||||
.bnr-right{display:flex;align-items:center;gap:10px;flex-shrink:0;}
|
||||
|
||||
@media(max-width:540px){
|
||||
.bnr-inner{flex-wrap:wrap;gap:10px;}
|
||||
.bnr-right{
|
||||
width:100%;
|
||||
display:flex;
|
||||
justify-content:space-between;
|
||||
align-items:center;
|
||||
gap:8px;
|
||||
flex-wrap:wrap;
|
||||
}
|
||||
.bnr-prob-num{font-size:22px!important;}
|
||||
}
|
||||
|
||||
/* ── STATUS ROW ── */
|
||||
.st-row{display:flex;flex-wrap:wrap;align-items:flex-start;gap:12px;}
|
||||
.st-dates{display:flex;gap:18px;flex-wrap:wrap;}
|
||||
|
||||
@media(max-width:480px){
|
||||
.st-dates{flex-direction:column;gap:8px;}
|
||||
}
|
||||
|
||||
/* ── FOTO GRID: max 2 kolom di mobile ── */
|
||||
@media(max-width:540px){
|
||||
.foto-g{grid-template-columns:1fr 1fr!important;}
|
||||
}
|
||||
@media(max-width:360px){
|
||||
.foto-g{grid-template-columns:1fr!important;}
|
||||
}
|
||||
</style>
|
||||
|
||||
@php
|
||||
|
|
@ -82,7 +147,6 @@
|
|||
};
|
||||
@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>
|
||||
|
|
@ -96,53 +160,57 @@
|
|||
</div>
|
||||
@endif
|
||||
|
||||
{{-- BANNER --}}
|
||||
<div style="position:relative;overflow:hidden;background:linear-gradient(135deg,#1e40af 0%,#2563eb 55%,#3b82f6 100%);border-radius:18px;padding:16px 20px;margin-bottom:12px;display:flex;align-items:center;gap:14px;box-shadow:0 6px 20px rgba(37,99,235,.22);">
|
||||
{{-- ══ BANNER ══ --}}
|
||||
<div style="position:relative;overflow:hidden;background:linear-gradient(135deg,#1e40af 0%,#2563eb 55%,#3b82f6 100%);border-radius:18px;padding:16px 20px;margin-bottom:12px;box-shadow:0 6px 20px rgba(37,99,235,.22);">
|
||||
<div style="position:absolute;top:-24px;right:-24px;width:110px;height:110px;background:rgba(255,255,255,.08);border-radius:50%;pointer-events:none;"></div>
|
||||
<div style="position:absolute;bottom:-28px;left:42%;width:80px;height:80px;background:rgba(255,255,255,.06);border-radius:50%;pointer-events:none;"></div>
|
||||
|
||||
<div 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 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 class="bnr-inner">
|
||||
{{-- Avatar --}}
|
||||
<div style="width:46px;height:46px;border-radius:13px;background:rgba(255,255,255,.18);border:2px solid rgba(255,255,255,.25);display:flex;align-items:center;justify-content:center;font-size:20px;font-weight:900;color:#fff;flex-shrink:0;">
|
||||
{{ strtoupper(substr($calonPenerima->nama_lengkap ?? 'U', 0, 1)) }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<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>
|
||||
{{-- Nama + NIK + status --}}
|
||||
<div class="bnr-mid">
|
||||
<h3 style="font-size:15px;font-weight:900;color:#fff;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;line-height:1.2;">{{ $calonPenerima->nama_lengkap ?? '-' }}</h3>
|
||||
<p style="font-size:11px;color:rgba(191,219,254,.85);font-family:monospace;margin-top:2px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;">NIK: {{ $calonPenerima->nik ?? '-' }}</p>
|
||||
<div style="margin-top:6px;">
|
||||
<span style="display:inline-flex;align-items:center;gap:5px;padding:4px 10px;border-radius:999px;background:rgba(255,255,255,.15);color:#fff;font-size:10.5px;font-weight:700;border:1px solid rgba(255,255,255,.22);">
|
||||
<span style="width:6px;height:6px;border-radius:50%;background:#fff;opacity:.9;"></span>
|
||||
{{ $trackingLabel }}
|
||||
</span>
|
||||
</div>
|
||||
@endif
|
||||
</div>
|
||||
|
||||
@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>
|
||||
{{-- Probabilitas + pill status --}}
|
||||
<div class="bnr-right">
|
||||
@if($probPct !== null)
|
||||
<div style="text-align:right;">
|
||||
<div class="bnr-prob-num" style="font-size:26px;font-weight:900;color:#fff;line-height:1;">{{ number_format($probPct,0) }}<span style="font-size:13px;color:rgba(191,219,254,.7);">%</span></div>
|
||||
<div style="font-size:10px;color:rgba(191,219,254,.6);margin-top:1px;">probabilitas</div>
|
||||
</div>
|
||||
@endif
|
||||
@else
|
||||
<span class="pill" style="background:#eff6ff;color:#1d4ed8;border:1px solid #bfdbfe;"><span class="dot" style="background:#3b82f6;"></span>{{ $trackingLabel }}</span>
|
||||
@endif
|
||||
@if($tracking === 'selesai')
|
||||
@if($st === 'disetujui')
|
||||
<span class="pill" style="background:#f0fdf4;color:#166534;border:1px solid #bbf7d0;"><span class="dot" style="background:#22c55e;"></span>Diterima</span>
|
||||
@elseif($st === 'ditolak')
|
||||
<span class="pill" style="background:#fff1f2;color:#9f1239;border:1px solid #fecdd3;"><span class="dot" style="background:#f43f5e;"></span>Tidak Diterima</span>
|
||||
@else
|
||||
<span class="pill" style="background:#fffbeb;color:#b45309;border:1px solid #fde68a;"><span class="dot" style="background:#f59e0b;"></span>Menunggu Hasil</span>
|
||||
@endif
|
||||
@else
|
||||
<span class="pill" style="background:#eff6ff;color:#1d4ed8;border:1px solid #bfdbfe;"><span class="dot" style="background:#3b82f6;"></span>{{ $trackingLabel }}</span>
|
||||
@endif
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{-- MAIN GRID --}}
|
||||
<div class="mg" style="display:grid;grid-template-columns:1fr 310px;gap:12px;align-items:start;">
|
||||
{{-- ══ MAIN GRID ══ --}}
|
||||
<div class="mg">
|
||||
|
||||
{{-- KIRI --}}
|
||||
<div style="display:flex;flex-direction:column;gap:12px;">
|
||||
{{-- ── KIRI ── --}}
|
||||
<div style="display:flex;flex-direction:column;gap:12px;min-width:0;">
|
||||
|
||||
<div class="g2">
|
||||
{{-- Identitas --}}
|
||||
|
|
@ -150,7 +218,7 @@
|
|||
<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">No. KK</p><p class="val" style="font-family:monospace;font-size:11px;word-break:break-all;">{{ $calonPenerima->no_kk ?? '-' }}</p></div>
|
||||
<div><p class="lbl">Jenis Kelamin</p><p class="val">{{ $calonPenerima->jenis_kelamin ?? '-' }}</p></div>
|
||||
<div><p class="lbl">Tempat Lahir</p><p class="val">{{ $calonPenerima->tempat_lahir ?? '-' }}</p></div>
|
||||
<div><p class="lbl">Tanggal Lahir</p><p class="val">{{ $calonPenerima->tanggal_lahir ? \Carbon\Carbon::parse($calonPenerima->tanggal_lahir)->translatedFormat('d F Y') : '-' }}</p></div>
|
||||
|
|
@ -173,6 +241,11 @@
|
|||
</div>
|
||||
</div>
|
||||
<div><p class="lbl">Aset Kepemilikan</p><p class="val">{{ $calonPenerima->aset_kepemilikan ?? '-' }}</p></div>
|
||||
<div class="g3">
|
||||
<div><p class="lbl">Kondisi Rumah</p><p class="val">{{ $calonPenerima->kondisi_rumah ?? '-' }}</p></div>
|
||||
<div><p class="lbl">Meteran Listrik</p><p class="val">{{ $calonPenerima->meteran_listrik ?? '-' }}</p></div>
|
||||
<div><p class="lbl">Sumber Air</p><p class="val">{{ $calonPenerima->sumber_air ?? '-' }}</p></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -197,7 +270,56 @@
|
|||
</div>
|
||||
</div>
|
||||
|
||||
{{-- Tracking Pengajuan --}}
|
||||
{{-- Dokumen & Foto --}}
|
||||
@php
|
||||
$fotoFields = [
|
||||
'foto_rumah_depan' => 'Foto Rumah Depan',
|
||||
'foto_rumah_belakang' => 'Foto Rumah Belakang',
|
||||
'foto_rumah_kanan' => 'Foto Rumah Kanan',
|
||||
'foto_rumah_kiri' => 'Foto Rumah Kiri',
|
||||
'foto_kk' => 'Foto KK',
|
||||
'foto_ktp' => 'Foto KTP',
|
||||
'foto_rekening_listrik' => 'Rekening Listrik',
|
||||
'foto_meteran_air' => 'Foto Meteran Air',
|
||||
'dokumen_pendukung' => 'Dokumen Pendukung',
|
||||
];
|
||||
$adaFoto = false;
|
||||
foreach($fotoFields as $field => $label) {
|
||||
if($calonPenerima->$field) { $adaFoto = true; break; }
|
||||
}
|
||||
@endphp
|
||||
@if($adaFoto)
|
||||
<div class="sp-card">
|
||||
<div class="sp-head"><div class="bar" style="background:#e11d48;"></div><h3>Dokumen & Foto</h3></div>
|
||||
<div class="sp-body">
|
||||
<div class="foto-g" style="display:grid;grid-template-columns:repeat(4,1fr);gap:10px;">
|
||||
@foreach($fotoFields as $field => $label)
|
||||
@if($calonPenerima->$field)
|
||||
<div>
|
||||
<p class="lbl" style="margin-bottom:5px;">{{ $label }}</p>
|
||||
@php $ext = pathinfo($calonPenerima->$field, PATHINFO_EXTENSION); @endphp
|
||||
@if(in_array(strtolower($ext), ['jpg','jpeg','png','gif','webp']))
|
||||
<a href="{{ Storage::url($calonPenerima->$field) }}" target="_blank">
|
||||
<img src="{{ Storage::url($calonPenerima->$field) }}"
|
||||
alt="{{ $label }}"
|
||||
style="width:100%;height:80px;object-fit:cover;border-radius:10px;border:1.5px solid #e5e7eb;">
|
||||
</a>
|
||||
@else
|
||||
<a href="{{ Storage::url($calonPenerima->$field) }}" target="_blank"
|
||||
style="display:inline-flex;align-items:center;gap:5px;padding:5px 10px;background:#eff6ff;color:#2563eb;font-size:11px;font-weight:700;border-radius:8px;text-decoration:none;border:1px solid #bfdbfe;">
|
||||
<svg width="12" height="12" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 10v6m0 0l-3-3m3 3l3-3m2 8H7a2 2 0 01-2-2V5a2 2 0 012-2h5l2 2h5a2 2 0 012 2v7"/></svg>
|
||||
Lihat File
|
||||
</a>
|
||||
@endif
|
||||
</div>
|
||||
@endif
|
||||
@endforeach
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@endif
|
||||
|
||||
{{-- Tracking --}}
|
||||
<div class="sp-card">
|
||||
<div class="sp-head"><div class="bar" style="background:#6b7280;"></div><h3>Tracking Pengajuan</h3></div>
|
||||
<div class="sp-body">
|
||||
|
|
@ -210,7 +332,6 @@
|
|||
</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>
|
||||
|
|
@ -225,7 +346,6 @@
|
|||
</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>
|
||||
|
|
@ -240,7 +360,6 @@
|
|||
</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>
|
||||
|
|
@ -261,40 +380,42 @@
|
|||
{{-- 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="pill" style="background:#fffbeb;color:#b45309;border:1px solid #fde68a;"><span class="dot" style="background:#f59e0b;"></span>Menunggu Hasil</span>
|
||||
<div class="sp-body">
|
||||
<div class="st-row">
|
||||
@if($tracking === 'draft')
|
||||
<span class="pill" style="background:#f3f4f6;color:#6b7280;border:1px solid #e5e7eb;"><span class="dot" style="background:#9ca3af;"></span>Draft</span>
|
||||
@elseif($tracking === 'terkirim')
|
||||
<span class="pill" style="background:#eff6ff;color:#1d4ed8;border:1px solid #bfdbfe;"><span class="dot" style="background:#3b82f6;"></span>Terkirim ke Kelurahan</span>
|
||||
@elseif($tracking === 'sedang_validasi')
|
||||
<span class="pill" style="background:#fffbeb;color:#b45309;border:1px solid #fde68a;"><span class="dot" style="background:#f59e0b;"></span>Sedang Divalidasi</span>
|
||||
@elseif($tracking === 'selesai')
|
||||
@if($st === 'disetujui')
|
||||
<span class="pill" style="background:#f0fdf4;color:#166534;border:1px solid #bbf7d0;"><span class="dot" style="background:#22c55e;"></span>Diterima</span>
|
||||
@elseif($st === 'ditolak')
|
||||
<span class="pill" style="background:#fff1f2;color:#9f1239;border:1px solid #fecdd3;"><span class="dot" style="background:#f43f5e;"></span>Tidak Diterima</span>
|
||||
@else
|
||||
<span class="pill" style="background:#fffbeb;color:#b45309;border:1px solid #fde68a;"><span class="dot" style="background:#f59e0b;"></span>Menunggu Hasil</span>
|
||||
@endif
|
||||
@endif
|
||||
@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 class="st-dates">
|
||||
<div><p class="lbl">Dibuat</p><p class="val">{{ $calonPenerima->created_at ? \Carbon\Carbon::parse($calonPenerima->created_at)->translatedFormat('d M Y, H:i') : '-' }}</p></div>
|
||||
<div><p class="lbl">Diperbarui</p><p class="val">{{ $calonPenerima->updated_at ? \Carbon\Carbon::parse($calonPenerima->updated_at)->translatedFormat('d M Y, H:i') : '-' }}</p></div>
|
||||
</div>
|
||||
@endif
|
||||
|
||||
@if(!empty($calonPenerima->catatan_admin))
|
||||
<div style="flex:1;min-width:0;width:100%;background:#f9fafb;border:1px solid #e5e7eb;border-radius:10px;padding:7px 11px;font-size:11.5px;color:#6b7280;">
|
||||
<strong style="color:#374151;">Catatan: </strong>{{ $calonPenerima->catatan_admin }}
|
||||
</div>
|
||||
@endif
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>{{-- end kiri --}}
|
||||
|
||||
{{-- KANAN --}}
|
||||
<div style="display:flex;flex-direction:column;gap:12px;">
|
||||
{{-- ── KANAN (sidebar) ── --}}
|
||||
<div style="display:flex;flex-direction:column;gap:12px;min-width:0;">
|
||||
|
||||
{{-- Prediksi --}}
|
||||
<div class="sp-card">
|
||||
|
|
@ -311,7 +432,7 @@
|
|||
{{ number_format($probPct,0) }}<span style="font-size:9px;color:#9ca3af;">%</span>
|
||||
</div>
|
||||
</div>
|
||||
<div style="flex:1;">
|
||||
<div style="flex:1;min-width:0;">
|
||||
<p class="lbl" style="margin-bottom:4px;">Rekomendasi</p>
|
||||
<span style="{{ $recBg }}display:inline-block;padding:3px 10px;border-radius:8px;font-size:11.5px;font-weight:700;">{{ $rec }}</span>
|
||||
<div style="margin-top:7px;height:4px;background:#f1f5f9;border-radius:99px;overflow:hidden;">
|
||||
|
|
@ -338,8 +459,18 @@
|
|||
<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>
|
||||
@foreach([
|
||||
'Pekerjaan' => $calonPenerima->pekerjaan??'-',
|
||||
'Penghasilan' => 'Rp '.number_format((float)($calonPenerima->penghasilan??0),0,',','.'),
|
||||
'Tanggungan' => ($calonPenerima->jumlah_tanggungan??'-').' orang',
|
||||
'Aset' => $calonPenerima->aset_kepemilikan??'-',
|
||||
'Bantuan Lain' => ucfirst($calonPenerima->bantuan_lain??'-'),
|
||||
'Usia' => ($calonPenerima->usia??'-').' tahun',
|
||||
'Kondisi Rumah' => $calonPenerima->kondisi_rumah??'-',
|
||||
'Meteran Listrik'=> $calonPenerima->meteran_listrik??'-',
|
||||
'Sumber Air' => $calonPenerima->sumber_air??'-',
|
||||
] as $lbl=>$val)
|
||||
<div style="display:flex;justify-content:space-between;gap:8px;font-size:11px;"><span style="color:#9ca3af;">{{ $lbl }}</span><span style="font-weight:600;color:#374151;text-align:right;word-break:break-word;">{{ $val }}</span></div>
|
||||
@endforeach
|
||||
</div>
|
||||
@if(!empty($explanation['positive']))
|
||||
|
|
@ -415,7 +546,8 @@
|
|||
</div>
|
||||
@endif
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>{{-- end kanan --}}
|
||||
|
||||
</div>{{-- end .mg --}}
|
||||
|
||||
</x-app-layout>
|
||||
|
|
@ -1,238 +1,220 @@
|
|||
<x-app-layout>
|
||||
<x-slot name="header">
|
||||
<div style="display:flex;align-items:center;justify-content:space-between;gap:12px;flex-wrap:wrap;">
|
||||
<div style="display:flex;align-items:center;justify-content:space-between;gap:10px;flex-wrap:wrap;">
|
||||
<div>
|
||||
<h2 style="font-size:17px;font-weight:900;color:#0f172a;line-height:1.2;letter-spacing:-.02em;">Dashboard</h2>
|
||||
<h2 style="font-size:16px;font-weight:900;color:#0f172a;letter-spacing:-.02em;">Dashboard</h2>
|
||||
<p style="font-size:11px;color:#94a3b8;margin-top:2px;font-weight:500;">
|
||||
Ringkasan data pendataan warga {{ Auth::user()->rt->dusun->nama_dusun ?? '' }}
|
||||
Pendataan warga {{ Auth::user()->rt->dusun->nama_dusun ?? '' }}
|
||||
</p>
|
||||
</div>
|
||||
<div style="display:inline-flex;align-items:center;gap:6px;background:#fff;border:1.5px solid #e2e8f0;border-radius:11px;padding:7px 12px;font-size:11px;color:#64748b;font-weight:500;box-shadow:0 1px 4px rgba(0,0,0,.04);">
|
||||
<svg width="13" height="13" fill="none" stroke="#3b82f6" viewBox="0 0 24 24">
|
||||
<div style="display:inline-flex;align-items:center;gap:6px;background:#fff;border:1.5px solid #e2e8f0;border-radius:11px;padding:7px 12px;font-size:11px;color:#64748b;font-weight:500;flex-shrink:0;min-width:0;overflow:hidden;">
|
||||
<svg width="13" height="13" fill="none" stroke="#3b82f6" viewBox="0 0 24 24" style="flex-shrink:0;">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 7V3m8 4V3m-9 8h10M5 21h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v12a2 2 0 002 2z"/>
|
||||
</svg>
|
||||
<span id="live-clock">
|
||||
{{ \Carbon\Carbon::now('Asia/Jakarta')->translatedFormat('l, d F Y') }}
|
||||
—
|
||||
{{ \Carbon\Carbon::now('Asia/Jakarta')->format('H:i') }} WIB
|
||||
</span>
|
||||
<span id="live-clock" style="white-space:nowrap;overflow:hidden;text-overflow:ellipsis;">{{ \Carbon\Carbon::now('Asia/Jakarta')->translatedFormat('l, d F Y') }} — {{ \Carbon\Carbon::now('Asia/Jakarta')->format('H:i') }} WIB</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
function updateClock() {
|
||||
const now = new Date();
|
||||
const options = { timeZone: 'Asia/Jakarta', weekday: 'long', day: '2-digit', month: 'long', year: 'numeric' };
|
||||
const dateStr = now.toLocaleDateString('id-ID', options);
|
||||
const timeStr = now.toLocaleTimeString('id-ID', { timeZone: 'Asia/Jakarta', hour: '2-digit', minute: '2-digit', hour12: false });
|
||||
const el = document.getElementById('live-clock');
|
||||
if (el) el.textContent = dateStr + ' — ' + timeStr + ' WIB';
|
||||
function updateClock(){
|
||||
var now=new Date();
|
||||
var d=now.toLocaleDateString('id-ID',{timeZone:'Asia/Jakarta',weekday:'long',day:'2-digit',month:'long',year:'numeric'});
|
||||
var t=now.toLocaleTimeString('id-ID',{timeZone:'Asia/Jakarta',hour:'2-digit',minute:'2-digit',hour12:false});
|
||||
var el=document.getElementById('live-clock');
|
||||
if(el) el.textContent=d+' — '+t+' WIB';
|
||||
}
|
||||
updateClock();
|
||||
setInterval(updateClock, 1000);
|
||||
setInterval(updateClock,1000);
|
||||
</script>
|
||||
</x-slot>
|
||||
|
||||
@php
|
||||
$avgProb = \App\Models\PrediksiKelayakan::whereHas('calonPenerima', function ($q) {
|
||||
$q->where('user_id', Auth::id());
|
||||
})->avg('probability');
|
||||
|
||||
$avgProb = $avgProb ?? 0;
|
||||
|
||||
if ($avgProb <= 1) {
|
||||
$avgProb = $avgProb * 100;
|
||||
}
|
||||
})->avg('probability') ?? 0;
|
||||
if ($avgProb <= 1) $avgProb = $avgProb * 100;
|
||||
@endphp
|
||||
|
||||
<style>
|
||||
.db-card{background:#fff;border-radius:20px;border:1.5px solid #f1f5f9;box-shadow:0 2px 8px rgba(0,0,0,.04);overflow:hidden;}
|
||||
.sc-blob1{position:absolute;top:-16px;right:-16px;width:80px;height:80px;background:rgba(255,255,255,.12);border-radius:50%;}
|
||||
.sc-blob2{position:absolute;bottom:-20px;right:8px;width:56px;height:56px;background:rgba(255,255,255,.09);border-radius:50%;}
|
||||
.db-card{background:#fff;border-radius:18px;border:1.5px solid #f1f5f9;box-shadow:0 2px 8px rgba(0,0,0,.04);overflow:hidden;}
|
||||
.sc-blob1{position:absolute;top:-16px;right:-16px;width:80px;height:80px;background:rgba(255,255,255,.12);border-radius:50%;pointer-events:none;}
|
||||
.sc-blob2{position:absolute;bottom:-20px;right:8px;width:56px;height:56px;background:rgba(255,255,255,.09);border-radius:50%;pointer-events:none;}
|
||||
|
||||
.aksi-btn{display:flex;align-items:center;gap:12px;padding:12px 14px;border-radius:14px;border:1.5px solid;text-decoration:none;transition:filter .15s,transform .1s;}
|
||||
.aksi-btn{display:flex;align-items:center;gap:12px;padding:12px 14px;border-radius:13px;border:1.5px solid;text-decoration:none;transition:filter .15s,transform .1s;}
|
||||
.aksi-btn:hover{filter:brightness(.96);transform:translateY(-1px);}
|
||||
.aksi-icon{width:38px;height:38px;border-radius:11px;display:flex;align-items:center;justify-content:center;flex-shrink:0;}
|
||||
.aksi-icon{width:36px;height:36px;border-radius:10px;display:flex;align-items:center;justify-content:center;flex-shrink:0;}
|
||||
.aksi-icon svg{width:16px;height:16px;stroke:#fff;}
|
||||
|
||||
.recent-item{display:flex;align-items:center;justify-content:space-between;padding:11px 16px;border-bottom:1px solid #f8fafc;transition:background .1s;}
|
||||
.recent-item{display:flex;align-items:center;justify-content:space-between;gap:8px;padding:11px 14px;border-bottom:1px solid #f8fafc;transition:background .1s;}
|
||||
.recent-item:hover{background:#f0f7ff;}
|
||||
.recent-item:last-child{border-bottom:none;}
|
||||
|
||||
/* ── STAT GRID ── */
|
||||
.rt-stat-grid{
|
||||
display:grid;
|
||||
grid-template-columns:repeat(4,1fr);
|
||||
gap:12px;
|
||||
}
|
||||
@media(max-width:860px){
|
||||
.rt-stat-grid{grid-template-columns:repeat(2,1fr);}
|
||||
}
|
||||
@media(max-width:380px){
|
||||
.rt-stat-grid{grid-template-columns:1fr;}
|
||||
}
|
||||
|
||||
/* ── MAIN GRID ── */
|
||||
.rt-main-grid{
|
||||
display:grid;
|
||||
grid-template-columns:2fr 1fr;
|
||||
gap:14px;
|
||||
align-items:start;
|
||||
}
|
||||
@media(max-width:1024px){
|
||||
.rt-main-grid{grid-template-columns:1fr;}
|
||||
}
|
||||
|
||||
/* recent-item wrap di layar kecil */
|
||||
@media(max-width:480px){
|
||||
.recent-item{flex-wrap:wrap;gap:6px;}
|
||||
.recent-item-right{width:100%;display:flex;justify-content:flex-end;}
|
||||
}
|
||||
|
||||
/* clock kecil di mobile */
|
||||
@media(max-width:500px){
|
||||
#live-clock{font-size:10px;}
|
||||
}
|
||||
|
||||
@keyframes pulse{
|
||||
0%,100%{opacity:1;transform:scale(1);}
|
||||
50%{opacity:.5;transform:scale(.85);}
|
||||
}
|
||||
</style>
|
||||
|
||||
<div style="display:flex;flex-direction:column;gap:14px;">
|
||||
|
||||
{{-- GREETING (asli tidak diubah) --}}
|
||||
<div style="display:flex;align-items:center;gap:12px;background:#fff;border:1.5px solid #f1f5f9;border-radius:18px;padding:14px 18px;box-shadow:0 1px 4px rgba(0,0,0,.04);">
|
||||
{{-- GREETING --}}
|
||||
<div style="display:flex;align-items:center;gap:12px;background:#fff;border:1.5px solid #f1f5f9;border-radius:16px;padding:14px 16px;box-shadow:0 1px 4px rgba(0,0,0,.04);">
|
||||
<div style="width:40px;height:40px;border-radius:50%;background:linear-gradient(135deg,#3b82f6,#2563eb);display:flex;align-items:center;justify-content:center;color:#fff;font-size:15px;font-weight:800;flex-shrink:0;box-shadow:0 4px 12px rgba(37,99,235,.28);">
|
||||
{{ strtoupper(substr(Auth::user()->name ?? 'U', 0, 1)) }}
|
||||
</div>
|
||||
<div>
|
||||
<p style="font-size:13.5px;font-weight:700;color:#0f172a;">
|
||||
Selamat datang, <span style="color:#2563eb;">{{ Auth::user()->name ?? 'Pengguna' }}</span> 👋
|
||||
</p>
|
||||
<div style="min-width:0;">
|
||||
<p style="font-size:13px;font-weight:700;color:#0f172a;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;">Selamat datang, <span style="color:#2563eb;">{{ Auth::user()->name ?? 'Pengguna' }}</span> 👋</p>
|
||||
<p style="font-size:11px;color:#94a3b8;margin-top:2px;font-weight:500;">Berikut ringkasan data terkini yang perlu Anda pantau.</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{-- STAT CARDS (asli tidak diubah) --}}
|
||||
<div style="display:grid;grid-template-columns:repeat(4,1fr);gap:12px;">
|
||||
{{-- STAT CARDS --}}
|
||||
<div class="rt-stat-grid">
|
||||
|
||||
<div style="position:relative;background:linear-gradient(135deg,#2563eb,#1d4ed8);color:#fff;border-radius:18px;padding:18px;display:flex;align-items:center;justify-content:space-between;overflow:hidden;box-shadow:0 6px 20px rgba(37,99,235,.3);">
|
||||
@php
|
||||
$rtCards = [
|
||||
['label'=>'Total Warga','val'=>$stats['total_input'],'grad'=>'#2563eb,#1d4ed8','shadow'=>'rgba(37,99,235,.3)','tc'=>'rgba(191,219,254,.9)','icon'=>'M17 20h5v-2a3 3 0 00-5.356-1.857M17 20H7m10 0v-2c0-.656-.126-1.283-.356-1.857M7 20H2v-2a3 3 0 015.356-1.857M7 20v-2c0-.656.126-1.283.356-1.857m0 0a5.002 5.002 0 019.288 0M15 7a3 3 0 11-6 0 3 3 0 016 0z'],
|
||||
['label'=>'Pending','val'=>$stats['pending'],'grad'=>'#f59e0b,#d97706','shadow'=>'rgba(245,158,11,.3)','tc'=>'rgba(254,243,199,.9)','icon'=>'M12 8v4l3 3m6-3a9 9 0 11-18 0 9 9 0 0118 0z'],
|
||||
['label'=>'Diterima','val'=>$stats['disetujui'],'grad'=>'#10b981,#059669','shadow'=>'rgba(16,185,129,.3)','tc'=>'rgba(167,243,208,.9)','icon'=>'M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z'],
|
||||
['label'=>'Ditolak','val'=>$stats['ditolak'],'grad'=>'#f43f5e,#e11d48','shadow'=>'rgba(244,63,94,.3)','tc'=>'rgba(254,205,211,.9)','icon'=>'M10 14l2-2m0 0l2-2m-2 2l-2-2m2 2l2 2m7-2a9 9 0 11-18 0 9 9 0 0118 0z'],
|
||||
];
|
||||
@endphp
|
||||
|
||||
@foreach($rtCards as $c)
|
||||
<div style="position:relative;background:linear-gradient(135deg,{{ $c['grad'] }});color:#fff;border-radius:16px;padding:16px;display:flex;align-items:center;justify-content:space-between;overflow:hidden;box-shadow:0 6px 20px {{ $c['shadow'] }};">
|
||||
<div class="sc-blob1"></div><div class="sc-blob2"></div>
|
||||
<div style="position:relative;z-index:1;">
|
||||
<p style="font-size:10.5px;font-weight:600;color:rgba(191,219,254,.9);">Total Warga</p>
|
||||
<p style="font-size:32px;font-weight:900;margin-top:2px;line-height:1;letter-spacing:-.04em;">{{ $stats['total_input'] }}</p>
|
||||
<p style="font-size:10px;font-weight:600;color:{{ $c['tc'] }};">{{ $c['label'] }}</p>
|
||||
<p style="font-size:28px;font-weight:900;margin-top:2px;line-height:1;letter-spacing:-.04em;">{{ $c['val'] }}</p>
|
||||
</div>
|
||||
<div style="position:relative;z-index:1;width:44px;height:44px;background:rgba(255,255,255,.18);border-radius:13px;display:flex;align-items:center;justify-content:center;flex-shrink:0;border:1px solid rgba(255,255,255,.2);">
|
||||
<svg width="22" height="22" fill="none" stroke="#fff" 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>
|
||||
</div>
|
||||
|
||||
<div style="position:relative;background:linear-gradient(135deg,#f59e0b,#d97706);color:#fff;border-radius:18px;padding:18px;display:flex;align-items:center;justify-content:space-between;overflow:hidden;box-shadow:0 6px 20px rgba(245,158,11,.3);">
|
||||
<div class="sc-blob1"></div><div class="sc-blob2"></div>
|
||||
<div style="position:relative;z-index:1;">
|
||||
<p style="font-size:10.5px;font-weight:600;color:rgba(254,243,199,.9);">Pending</p>
|
||||
<p style="font-size:32px;font-weight:900;margin-top:2px;line-height:1;letter-spacing:-.04em;">{{ $stats['pending'] }}</p>
|
||||
</div>
|
||||
<div style="position:relative;z-index:1;width:44px;height:44px;background:rgba(255,255,255,.18);border-radius:13px;display:flex;align-items:center;justify-content:center;flex-shrink:0;border:1px solid rgba(255,255,255,.2);">
|
||||
<svg width="22" height="22" fill="none" stroke="#fff" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 8v4l3 3m6-3a9 9 0 11-18 0 9 9 0 0118 0z"/></svg>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div style="position:relative;background:linear-gradient(135deg,#10b981,#059669);color:#fff;border-radius:18px;padding:18px;display:flex;align-items:center;justify-content:space-between;overflow:hidden;box-shadow:0 6px 20px rgba(16,185,129,.3);">
|
||||
<div class="sc-blob1"></div><div class="sc-blob2"></div>
|
||||
<div style="position:relative;z-index:1;">
|
||||
<p style="font-size:10.5px;font-weight:600;color:rgba(167,243,208,.9);">Diterima</p>
|
||||
<p style="font-size:32px;font-weight:900;margin-top:2px;line-height:1;letter-spacing:-.04em;">{{ $stats['disetujui'] }}</p>
|
||||
</div>
|
||||
<div style="position:relative;z-index:1;width:44px;height:44px;background:rgba(255,255,255,.18);border-radius:13px;display:flex;align-items:center;justify-content:center;flex-shrink:0;border:1px solid rgba(255,255,255,.2);">
|
||||
<svg width="22" height="22" fill="none" stroke="#fff" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z"/></svg>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div style="position:relative;background:linear-gradient(135deg,#f43f5e,#e11d48);color:#fff;border-radius:18px;padding:18px;display:flex;align-items:center;justify-content:space-between;overflow:hidden;box-shadow:0 6px 20px rgba(244,63,94,.3);">
|
||||
<div class="sc-blob1"></div><div class="sc-blob2"></div>
|
||||
<div style="position:relative;z-index:1;">
|
||||
<p style="font-size:10.5px;font-weight:600;color:rgba(254,205,211,.9);">Ditolak</p>
|
||||
<p style="font-size:32px;font-weight:900;margin-top:2px;line-height:1;letter-spacing:-.04em;">{{ $stats['ditolak'] }}</p>
|
||||
</div>
|
||||
<div style="position:relative;z-index:1;width:44px;height:44px;background:rgba(255,255,255,.18);border-radius:13px;display:flex;align-items:center;justify-content:center;flex-shrink:0;border:1px solid rgba(255,255,255,.2);">
|
||||
<svg width="22" height="22" fill="none" stroke="#fff" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10 14l2-2m0 0l2-2m-2 2l-2-2m2 2l2 2m7-2a9 9 0 11-18 0 9 9 0 0118 0z"/></svg>
|
||||
<div style="position:relative;z-index:1;width:38px;height:38px;background:rgba(255,255,255,.18);border-radius:11px;display:flex;align-items:center;justify-content:center;flex-shrink:0;border:1px solid rgba(255,255,255,.2);">
|
||||
<svg width="18" height="18" fill="none" stroke="#fff" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="{{ $c['icon'] }}"/></svg>
|
||||
</div>
|
||||
</div>
|
||||
@endforeach
|
||||
|
||||
</div>
|
||||
|
||||
{{-- GRID: Pendataan Terbaru + Aksi Cepat (logika asli 100% tidak diubah) --}}
|
||||
<div style="display:grid;grid-template-columns:2fr 1fr;gap:14px;">
|
||||
{{-- GRID: Terbaru + Aksi --}}
|
||||
<div class="rt-main-grid">
|
||||
|
||||
{{-- Pendataan Terbaru --}}
|
||||
<div class="db-card">
|
||||
<div style="padding:12px 18px;border-bottom:1px solid #f1f5f9;background:#fafbfc;display:flex;align-items:center;justify-content:space-between;">
|
||||
<div style="padding:12px 16px;border-bottom:1px solid #f1f5f9;background:#fafbfc;display:flex;align-items:center;justify-content:space-between;">
|
||||
<div style="display:flex;align-items:center;gap:7px;">
|
||||
<div style="width:3px;height:16px;background:linear-gradient(180deg,#3b82f6,#2563eb);border-radius:4px;"></div>
|
||||
<div style="width:3px;height:15px;background:linear-gradient(180deg,#3b82f6,#2563eb);border-radius:4px;"></div>
|
||||
<h3 style="font-size:13px;font-weight:700;color:#1e293b;">Pendataan Terbaru</h3>
|
||||
</div>
|
||||
<span style="display:inline-flex;align-items:center;gap:4px;font-size:10px;color:#10b981;background:#f0fdf4;border:1px solid #bbf7d0;padding:2px 9px;border-radius:20px;font-weight:700;">
|
||||
<span style="width:5px;height:5px;border-radius:50%;background:#10b981;animation:pulse 2s infinite;"></span>
|
||||
Live
|
||||
<span style="display:inline-flex;align-items:center;gap:4px;font-size:10px;color:#10b981;background:#f0fdf4;border:1px solid #bbf7d0;padding:2px 9px;border-radius:20px;font-weight:700;flex-shrink:0;">
|
||||
<span style="width:5px;height:5px;border-radius:50%;background:#10b981;animation:pulse 2s infinite;display:inline-block;"></span>Live
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
@forelse($recentCalonPenerima as $calon)
|
||||
<div class="recent-item">
|
||||
<div style="display:flex;align-items:center;gap:10px;">
|
||||
<div style="width:34px;height:34px;border-radius:10px;background:linear-gradient(135deg,#3b82f6,#2563eb);display:flex;align-items:center;justify-content:center;flex-shrink:0;font-size:12px;font-weight:800;color:#fff;box-shadow:0 3px 8px rgba(37,99,235,.22);">
|
||||
{{ strtoupper(substr($calon->nama_lengkap, 0, 1)) }}
|
||||
</div>
|
||||
<div>
|
||||
<p style="font-size:12.5px;font-weight:700;color:#0f172a;">{{ $calon->nama_lengkap }}</p>
|
||||
<p style="font-size:10.5px;color:#94a3b8;margin-top:1px;font-family:monospace;">NIK: {{ $calon->nik }}</p>
|
||||
</div>
|
||||
<div class="recent-item">
|
||||
<div style="display:flex;align-items:center;gap:10px;min-width:0;flex:1;">
|
||||
<div style="width:32px;height:32px;border-radius:9px;background:linear-gradient(135deg,#3b82f6,#2563eb);display:flex;align-items:center;justify-content:center;flex-shrink:0;font-size:12px;font-weight:800;color:#fff;">
|
||||
{{ strtoupper(substr($calon->nama_lengkap,0,1)) }}
|
||||
</div>
|
||||
<div style="text-align:right;">
|
||||
@if($calon->status_verifikasi == 'pending')
|
||||
<span style="display:inline-flex;align-items:center;gap:3px;padding:3px 9px;font-size:10.5px;font-weight:700;border-radius:20px;background:#fffbeb;color:#b45309;border:1px solid #fde68a;">
|
||||
<span style="width:5px;height:5px;border-radius:50%;background:#f59e0b;flex-shrink:0;"></span>
|
||||
Pending
|
||||
</span>
|
||||
@elseif($calon->status_verifikasi == 'disetujui')
|
||||
<span style="display:inline-flex;align-items:center;gap:3px;padding:3px 9px;font-size:10.5px;font-weight:700;border-radius:20px;background:#f0fdf4;color:#166534;border:1px solid #bbf7d0;">
|
||||
<span style="width:5px;height:5px;border-radius:50%;background:#22c55e;flex-shrink:0;"></span>
|
||||
Disetujui
|
||||
</span>
|
||||
@else
|
||||
<span style="display:inline-flex;align-items:center;gap:3px;padding:3px 9px;font-size:10.5px;font-weight:700;border-radius:20px;background:#fff1f2;color:#be123c;border:1px solid #fecdd3;">
|
||||
<span style="width:5px;height:5px;border-radius:50%;background:#f43f5e;flex-shrink:0;"></span>
|
||||
{{ ucfirst($calon->status_verifikasi) }}
|
||||
</span>
|
||||
@endif
|
||||
<p style="font-size:10px;color:#94a3b8;margin-top:3px;">{{ $calon->created_at->diffForHumans() }}</p>
|
||||
<div style="min-width:0;">
|
||||
<p style="font-size:12px;font-weight:700;color:#0f172a;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;max-width:180px;">{{ $calon->nama_lengkap }}</p>
|
||||
<p style="font-size:10px;color:#94a3b8;font-family:monospace;">{{ $calon->nik }}</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="recent-item-right" style="text-align:right;flex-shrink:0;">
|
||||
@php
|
||||
$sv=['pending'=>['#fffbeb','#b45309','#fde68a','Pending'],'disetujui'=>['#f0fdf4','#166534','#bbf7d0','Disetujui']];
|
||||
$sv=$sv[$calon->status_verifikasi]??['#fff1f2','#be123c','#fecdd3',ucfirst($calon->status_verifikasi)];
|
||||
@endphp
|
||||
<span style="display:inline-flex;align-items:center;gap:3px;padding:3px 9px;font-size:10px;font-weight:700;border-radius:20px;background:{{ $sv[0] }};color:{{ $sv[1] }};border:1px solid {{ $sv[2] }};white-space:nowrap;">
|
||||
<span style="width:5px;height:5px;border-radius:50%;background:{{ $sv[1] }};flex-shrink:0;display:inline-block;"></span>{{ $sv[3] }}
|
||||
</span>
|
||||
<p style="font-size:10px;color:#94a3b8;margin-top:3px;">{{ $calon->created_at->diffForHumans() }}</p>
|
||||
</div>
|
||||
</div>
|
||||
@empty
|
||||
<div style="padding:44px 16px;text-align:center;">
|
||||
<div style="width:44px;height:44px;background:#f1f5f9;border-radius:50%;display:flex;align-items:center;justify-content:center;margin:0 auto 10px;">
|
||||
<svg width="20" height="20" fill="none" stroke="#d1d5db" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2"/></svg>
|
||||
</div>
|
||||
<p style="font-size:12.5px;font-weight:600;color:#94a3b8;">Belum ada data pendataan</p>
|
||||
</div>
|
||||
<div style="padding:44px 16px;text-align:center;">
|
||||
<p style="font-size:12.5px;font-weight:600;color:#94a3b8;">Belum ada data pendataan</p>
|
||||
</div>
|
||||
@endforelse
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{-- Aksi Cepat (asli tidak diubah) --}}
|
||||
{{-- Aksi Cepat --}}
|
||||
<div class="db-card" style="padding:0;">
|
||||
<div style="padding:12px 18px;border-bottom:1px solid #f1f5f9;background:#fafbfc;display:flex;align-items:center;gap:7px;">
|
||||
<div style="width:3px;height:16px;background:linear-gradient(180deg,#10b981,#059669);border-radius:4px;"></div>
|
||||
<div style="padding:12px 16px;border-bottom:1px solid #f1f5f9;background:#fafbfc;display:flex;align-items:center;gap:7px;">
|
||||
<div style="width:3px;height:15px;background:linear-gradient(180deg,#10b981,#059669);border-radius:4px;"></div>
|
||||
<h3 style="font-size:13px;font-weight:700;color:#1e293b;">Aksi Cepat</h3>
|
||||
</div>
|
||||
<div style="padding:12px;display:flex;flex-direction:column;gap:10px;">
|
||||
|
||||
<div style="padding:14px;display:flex;flex-direction:column;gap:10px;">
|
||||
|
||||
{{-- Daftarkan Warga Baru (asli) --}}
|
||||
<a href="{{ route('rt.calon-penerima.create') }}" class="aksi-btn"
|
||||
style="background:#eff6ff;border-color:#bfdbfe;">
|
||||
<a href="{{ route('rt.calon-penerima.create') }}" class="aksi-btn" style="background:#eff6ff;border-color:#bfdbfe;">
|
||||
<div class="aksi-icon" style="background:linear-gradient(135deg,#3b82f6,#2563eb);box-shadow:0 4px 12px rgba(37,99,235,.28);">
|
||||
<svg viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M18 9v3m0 0v3m0-3h3m-3 0h-3m-2-5a4 4 0 11-8 0 4 4 0 018 0zM3 20a6 6 0 0112 0v1H3v-1z"/></svg>
|
||||
</div>
|
||||
<div>
|
||||
<p style="font-size:12.5px;font-weight:700;color:#1e293b;">Daftarkan Warga Baru</p>
|
||||
<div style="min-width:0;">
|
||||
<p style="font-size:12px;font-weight:700;color:#1e293b;">Daftarkan Warga Baru</p>
|
||||
<p style="font-size:10.5px;color:#64748b;margin-top:1px;">Tambah data calon penerima</p>
|
||||
</div>
|
||||
</a>
|
||||
|
||||
{{-- Lihat Laporan (asli) --}}
|
||||
<a href="{{ route('rt.laporan.index') }}" class="aksi-btn"
|
||||
style="background:#f0fdf4;border-color:#bbf7d0;">
|
||||
<a href="{{ route('rt.laporan.index') }}" class="aksi-btn" style="background:#f0fdf4;border-color:#bbf7d0;">
|
||||
<div class="aksi-icon" style="background:linear-gradient(135deg,#10b981,#059669);box-shadow:0 4px 12px rgba(16,185,129,.28);">
|
||||
<svg viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2"/></svg>
|
||||
</div>
|
||||
<div>
|
||||
<p style="font-size:12.5px;font-weight:700;color:#1e293b;">Lihat Laporan</p>
|
||||
<p style="font-size:10.5px;color:#64748b;margin-top:1px;">Lihat hasil akhir yang dikirim admin</p>
|
||||
<div style="min-width:0;">
|
||||
<p style="font-size:12px;font-weight:700;color:#1e293b;">Lihat Laporan</p>
|
||||
<p style="font-size:10.5px;color:#64748b;margin-top:1px;">Hasil akhir dari admin</p>
|
||||
</div>
|
||||
</a>
|
||||
|
||||
{{-- Rata-rata skor (asli, $avgProb tidak diubah) --}}
|
||||
<div style="padding:14px;background:linear-gradient(135deg,#1e40af,#2563eb,#3b82f6);border-radius:14px;box-shadow:0 6px 20px rgba(37,99,235,.3);position:relative;overflow:hidden;">
|
||||
<div style="position:absolute;top:-14px;right:-14px;width:70px;height:70px;background:rgba(255,255,255,.08);border-radius:50%;"></div>
|
||||
<div style="position:absolute;bottom:-18px;left:30%;width:54px;height:54px;background:rgba(255,255,255,.06);border-radius:50%;"></div>
|
||||
{{-- Skor rata-rata --}}
|
||||
<div style="padding:14px;background:linear-gradient(135deg,#1e40af,#2563eb,#3b82f6);border-radius:13px;box-shadow:0 6px 20px rgba(37,99,235,.3);position:relative;overflow:hidden;">
|
||||
<div style="position:absolute;top:-14px;right:-14px;width:70px;height:70px;background:rgba(255,255,255,.08);border-radius:50%;pointer-events:none;"></div>
|
||||
<div style="position:relative;z-index:1;">
|
||||
<div style="display:flex;align-items:center;justify-content:space-between;margin-bottom:4px;">
|
||||
<p style="font-size:10.5px;font-weight:700;color:rgba(255,255,255,.85);">Rata-rata Skor Kelayakan</p>
|
||||
<svg width="14" height="14" fill="none" stroke="rgba(191,219,254,.8)" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 7h8m0 0v8m0-8l-8 8-4-4-6 6"/></svg>
|
||||
</div>
|
||||
<p style="font-size:30px;font-weight:900;color:#fff;line-height:1;letter-spacing:-.04em;margin-bottom:4px;">
|
||||
{{ number_format($avgProb, 1) }}<span style="font-size:14px;font-weight:600;color:rgba(191,219,254,.8);">%</span>
|
||||
<p style="font-size:10px;font-weight:700;color:rgba(255,255,255,.85);margin-bottom:4px;">Rata-rata Skor Kelayakan</p>
|
||||
<p style="font-size:28px;font-weight:900;color:#fff;line-height:1;letter-spacing:-.04em;margin-bottom:4px;">
|
||||
{{ number_format($avgProb,1) }}<span style="font-size:13px;font-weight:600;color:rgba(191,219,254,.8);">%</span>
|
||||
</p>
|
||||
<div style="width:100%;height:4px;background:rgba(255,255,255,.2);border-radius:99px;overflow:hidden;margin-bottom:6px;">
|
||||
<div style="height:100%;border-radius:99px;background:rgba(255,255,255,.7);width:{{ min($avgProb, 100) }}%;"></div>
|
||||
<div style="height:100%;border-radius:99px;background:rgba(255,255,255,.7);width:{{ min($avgProb,100) }}%;"></div>
|
||||
</div>
|
||||
<p style="font-size:10px;color:rgba(191,219,254,.75);font-weight:500;line-height:1.5;">
|
||||
Nilai rata-rata prediksi kelayakan, bukan keputusan akhir bantuan
|
||||
Rata-rata prediksi, bukan keputusan akhir
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -244,11 +226,4 @@ function updateClock() {
|
|||
|
||||
</div>
|
||||
|
||||
<style>
|
||||
@keyframes pulse {
|
||||
0%, 100% { opacity: 1; transform: scale(1); }
|
||||
50% { opacity: .5; transform: scale(.85); }
|
||||
}
|
||||
</style>
|
||||
|
||||
</x-app-layout>
|
||||
|
|
@ -1,111 +1,103 @@
|
|||
<x-app-layout>
|
||||
|
||||
<style>
|
||||
.lk-wrap{max-width:1200px;margin:0 auto;padding:24px 20px 40px;}
|
||||
<x-slot name="header">
|
||||
<div style="display:flex;align-items:center;justify-content:space-between;gap:12px;flex-wrap:wrap;">
|
||||
<div>
|
||||
<h2 style="font-size:16px;font-weight:800;color:#0f172a;letter-spacing:-.02em;">Laporan Hasil Kelayakan</h2>
|
||||
<p style="font-size:11px;color:#94a3b8;margin-top:2px;">Data hasil akhir verifikasi warga yang telah dikirim admin kelurahan</p>
|
||||
</div>
|
||||
</div>
|
||||
</x-slot>
|
||||
|
||||
/* Card */
|
||||
.lk-card{background:#fff;border-radius:20px;border:1.5px solid #f1f5f9;box-shadow:0 2px 8px rgba(0,0,0,.05);overflow:hidden;margin-bottom:14px;}
|
||||
<style>
|
||||
.lk-card{background:#fff;border-radius:16px;border:1.5px solid #f1f5f9;box-shadow:0 2px 8px rgba(0,0,0,.05);overflow:hidden;margin-bottom:14px;}
|
||||
.sec-hd{padding:11px 16px;border-bottom:1.5px solid #f1f5f9;background:#fafbfc;display:flex;align-items:center;justify-content:space-between;flex-wrap:wrap;gap:8px;}
|
||||
.sec-hd-l{display:flex;align-items:center;gap:8px;}
|
||||
.sec-bar{width:3px;height:15px;background:linear-gradient(180deg,#3b82f6,#2563eb);border-radius:4px;}
|
||||
.sec-title{font-size:13px;font-weight:800;color:#1e293b;}
|
||||
.sec-count{font-size:11px;color:#94a3b8;background:#f1f5f9;padding:2px 8px;border-radius:20px;font-weight:600;}
|
||||
|
||||
/* Section header */
|
||||
.sec-hd{padding:12px 18px;border-bottom:1.5px solid #f1f5f9;background:#fafbfc;display:flex;align-items:center;justify-content:space-between;}
|
||||
.sec-hd-l{display:flex;align-items:center;gap:8px;}
|
||||
.sec-bar{width:3px;height:16px;background:linear-gradient(180deg,#3b82f6,#2563eb);border-radius:4px;}
|
||||
.sec-title{font-size:13px;font-weight:800;color:#1e293b;}
|
||||
.sec-count{font-size:11px;color:#94a3b8;background:#f1f5f9;padding:2px 8px;border-radius:20px;font-weight:600;}
|
||||
/* stat cards */
|
||||
.sum-grid{display:grid;grid-template-columns:repeat(3,1fr);gap:12px;margin-bottom:14px;}
|
||||
@media(max-width:560px){.sum-grid{grid-template-columns:1fr 1fr;}}
|
||||
.sum-card{background:#fff;border-radius:16px;border:1.5px solid #f1f5f9;box-shadow:0 1px 6px rgba(0,0,0,.04);padding:14px 16px;}
|
||||
.sum-icon{width:34px;height:34px;border-radius:10px;display:flex;align-items:center;justify-content:center;margin-bottom:8px;}
|
||||
.sum-icon svg{width:17px;height:17px;}
|
||||
.sum-val{font-size:24px;font-weight:900;color:#0f172a;line-height:1;letter-spacing:-.03em;}
|
||||
.sum-lbl{font-size:10px;color:#94a3b8;font-weight:700;margin-top:3px;text-transform:uppercase;letter-spacing:.04em;}
|
||||
|
||||
/* Summary */
|
||||
.sum-grid{display:grid;grid-template-columns:repeat(3,1fr);gap:12px;margin-bottom:14px;}
|
||||
@media(max-width:700px){.sum-grid{grid-template-columns:1fr 1fr;}}
|
||||
.sum-card{background:#fff;border-radius:18px;border:1.5px solid #f1f5f9;box-shadow:0 1px 6px rgba(0,0,0,.04);padding:14px 16px;}
|
||||
.sum-icon{width:34px;height:34px;border-radius:10px;display:flex;align-items:center;justify-content:center;margin-bottom:9px;}
|
||||
.sum-icon svg{width:17px;height:17px;}
|
||||
.sum-val{font-size:22px;font-weight:900;color:#0f172a;line-height:1;letter-spacing:-.03em;}
|
||||
.sum-lbl{font-size:10px;color:#94a3b8;font-weight:700;margin-top:3px;text-transform:uppercase;letter-spacing:.04em;}
|
||||
/* desktop table */
|
||||
.lk-tbl-wrap{overflow-x:auto;-webkit-overflow-scrolling:touch;}
|
||||
table.lk-tbl{width:100%;border-collapse:collapse;min-width:860px;}
|
||||
table.lk-tbl thead tr{background:#f8fafc;border-bottom:1.5px solid #f1f5f9;}
|
||||
table.lk-tbl thead th{padding:9px 11px;text-align:left;font-size:9.5px;font-weight:800;color:#94a3b8;text-transform:uppercase;letter-spacing:.07em;white-space:nowrap;}
|
||||
table.lk-tbl tbody tr{border-bottom:1px solid #f8fafc;transition:background .1s;}
|
||||
table.lk-tbl tbody tr:hover{background:#f0f7ff;}
|
||||
table.lk-tbl tbody tr:last-child{border-bottom:none;}
|
||||
table.lk-tbl tbody td{padding:9px 11px;vertical-align:middle;font-size:12px;color:#374151;}
|
||||
|
||||
/* Table */
|
||||
.lk-tbl-wrap{overflow-x:auto;}
|
||||
table.lk-tbl{width:100%;border-collapse:collapse;}
|
||||
table.lk-tbl thead tr{background:#f8fafc;border-bottom:1.5px solid #f1f5f9;}
|
||||
table.lk-tbl thead th{padding:10px 11px;text-align:left;font-size:9.5px;font-weight:800;color:#94a3b8;text-transform:uppercase;letter-spacing:.07em;white-space:nowrap;}
|
||||
table.lk-tbl tbody tr{border-bottom:1px solid #f8fafc;transition:background .1s;}
|
||||
table.lk-tbl tbody tr:hover{background:#f0f7ff;}
|
||||
table.lk-tbl tbody tr:last-child{border-bottom:none;}
|
||||
table.lk-tbl tbody td{padding:9px 11px;vertical-align:middle;font-size:12px;color:#374151;}
|
||||
/* mobile card */
|
||||
.mob-list{display:none;flex-direction:column;gap:10px;padding:14px;}
|
||||
@media(max-width:640px){
|
||||
.lk-tbl-wrap{display:none;}
|
||||
.mob-list{display:flex;}
|
||||
}
|
||||
.mob-card{background:#fafbfc;border:1.5px solid #f1f5f9;border-radius:13px;padding:13px;display:flex;flex-direction:column;gap:10px;}
|
||||
.mob-top{display:flex;align-items:center;gap:10px;}
|
||||
.mob-name{font-size:13px;font-weight:800;color:#0f172a;flex:1;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;}
|
||||
.mob-rows{display:grid;grid-template-columns:1fr 1fr;gap:6px 10px;}
|
||||
.mob-kv{display:flex;flex-direction:column;gap:2px;}
|
||||
.mob-k{font-size:9px;font-weight:700;color:#94a3b8;text-transform:uppercase;letter-spacing:.05em;}
|
||||
.mob-v{font-size:11.5px;font-weight:600;color:#374151;}
|
||||
.mob-bottom{display:flex;align-items:center;justify-content:space-between;flex-wrap:wrap;gap:6px;padding-top:8px;border-top:1px solid #f1f5f9;}
|
||||
|
||||
/* Cells */
|
||||
.av{width:30px;height:30px;border-radius:9px;display:flex;align-items:center;justify-content:center;font-size:11px;font-weight:800;color:#fff;flex-shrink:0;}
|
||||
.name-cell{display:flex;align-items:center;gap:8px;}
|
||||
.nik-pill{font-family:'Courier New',monospace;font-size:10px;color:#64748b;background:#f1f5f9;padding:2px 6px;border-radius:5px;}
|
||||
.rt-badge{display:inline-flex;padding:2px 7px;border-radius:6px;background:#eff6ff;color:#1d4ed8;font-size:10px;font-weight:800;border:1px solid #bfdbfe;}
|
||||
.prob-cell{display:flex;align-items:center;gap:6px;}
|
||||
.prob-bg{width:38px;height:4px;background:#f1f5f9;border-radius:99px;overflow:hidden;flex-shrink:0;}
|
||||
.prob-fill{height:100%;border-radius:99px;}
|
||||
.st-pill{display:inline-flex;align-items:center;gap:4px;padding:4px 10px;border-radius:20px;font-size:10.5px;font-weight:700;}
|
||||
.st-dot{width:5px;height:5px;border-radius:50%;flex-shrink:0;}
|
||||
/* shared */
|
||||
.av{width:30px;height:30px;border-radius:9px;display:flex;align-items:center;justify-content:center;font-size:11px;font-weight:800;color:#fff;flex-shrink:0;}
|
||||
.nik-pill{font-family:'Courier New',monospace;font-size:10px;color:#64748b;background:#f1f5f9;padding:2px 6px;border-radius:5px;}
|
||||
.rt-badge{display:inline-flex;padding:2px 7px;border-radius:6px;background:#eff6ff;color:#1d4ed8;font-size:10px;font-weight:800;border:1px solid #bfdbfe;}
|
||||
.prob-cell{display:flex;align-items:center;gap:6px;}
|
||||
.prob-bg{width:38px;height:4px;background:#f1f5f9;border-radius:99px;overflow:hidden;flex-shrink:0;}
|
||||
.prob-fill{height:100%;border-radius:99px;}
|
||||
.st-pill{display:inline-flex;align-items:center;gap:4px;padding:4px 10px;border-radius:20px;font-size:10.5px;font-weight:700;}
|
||||
.st-dot{width:5px;height:5px;border-radius:50%;flex-shrink:0;}
|
||||
.empty-state{padding:52px 16px;text-align:center;}
|
||||
.empty-icon{width:46px;height:46px;background:#f1f5f9;border-radius:50%;display:flex;align-items:center;justify-content:center;margin:0 auto 10px;}
|
||||
.tbl-foot{padding:9px 16px;border-top:1.5px solid #f1f5f9;background:#fafbfc;text-align:center;}
|
||||
.credit{font-size:11px;color:#cbd5e1;font-weight:500;}
|
||||
</style>
|
||||
|
||||
/* Empty */
|
||||
.empty-state{padding:52px 16px;text-align:center;}
|
||||
.empty-icon{width:46px;height:46px;background:#f1f5f9;border-radius:50%;display:flex;align-items:center;justify-content:center;margin:0 auto 10px;}
|
||||
.empty-icon svg{width:20px;height:20px;}
|
||||
|
||||
/* Footer */
|
||||
.tbl-foot{padding:9px 18px;border-top:1.5px solid #f1f5f9;background:#fafbfc;text-align:center;}
|
||||
.credit{font-size:11px;color:#cbd5e1;font-weight:500;}
|
||||
</style>
|
||||
|
||||
<div class="lk-wrap">
|
||||
|
||||
{{-- ── HEADER ── --}}
|
||||
<div style="margin-bottom:18px;">
|
||||
<h1 style="font-size:20px;font-weight:900;color:#0f172a;letter-spacing:-.03em;line-height:1.2;">
|
||||
Laporan Hasil Kelayakan
|
||||
</h1>
|
||||
<p style="font-size:11.5px;color:#94a3b8;margin-top:4px;">
|
||||
Data hasil akhir verifikasi warga yang telah dikirim oleh admin kelurahan
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{{-- ── SUMMARY ── --}}
|
||||
@php
|
||||
$total = $stats['total'] ?? $laporans->count();
|
||||
$diterima = $stats['diterima'] ?? $laporans->where('status_verifikasi', 'disetujui')->count();
|
||||
$ditolak = $stats['ditolak'] ?? $laporans->where('status_verifikasi', 'ditolak')->count();
|
||||
$total = $stats['total'] ?? $laporans->count();
|
||||
$diterima = $stats['diterima'] ?? $laporans->where('status_verifikasi','disetujui')->count();
|
||||
$ditolak = $stats['ditolak'] ?? $laporans->where('status_verifikasi','ditolak')->count();
|
||||
@endphp
|
||||
|
||||
{{-- STAT CARDS --}}
|
||||
<div class="sum-grid">
|
||||
<div class="sum-card">
|
||||
<div class="sum-icon" style="background:#eff6ff;">
|
||||
<svg fill="none" stroke="#2563eb" 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>
|
||||
<svg fill="none" stroke="#2563eb" 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>
|
||||
<div class="sum-val">{{ $total }}</div>
|
||||
<div class="sum-lbl">Total Warga</div>
|
||||
</div>
|
||||
<div class="sum-card">
|
||||
<div class="sum-icon" style="background:#f0fdf4;">
|
||||
<svg fill="none" stroke="#16a34a" 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>
|
||||
<svg fill="none" stroke="#16a34a" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z"/></svg>
|
||||
</div>
|
||||
<div class="sum-val" style="color:#16a34a;">{{ $diterima }}</div>
|
||||
<div class="sum-lbl">Diterima</div>
|
||||
</div>
|
||||
<div class="sum-card">
|
||||
<div class="sum-icon" style="background:#fff1f2;">
|
||||
<svg fill="none" stroke="#e11d48" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
|
||||
d="M10 14l2-2m0 0l2-2m-2 2l-2-2m2 2l2 2m7-2a9 9 0 11-18 0 9 9 0 0118 0z"/>
|
||||
</svg>
|
||||
<svg fill="none" stroke="#e11d48" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10 14l2-2m0 0l2-2m-2 2l-2-2m2 2l2 2m7-2a9 9 0 11-18 0 9 9 0 0118 0z"/></svg>
|
||||
</div>
|
||||
<div class="sum-val" style="color:#e11d48;">{{ $ditolak }}</div>
|
||||
<div class="sum-lbl">Ditolak</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{-- ── TABLE ── --}}
|
||||
{{-- TABLE CARD --}}
|
||||
<div class="lk-card">
|
||||
<div class="sec-hd">
|
||||
<div class="sec-hd-l">
|
||||
|
|
@ -115,11 +107,12 @@
|
|||
<span class="sec-count">{{ $total }} data</span>
|
||||
</div>
|
||||
|
||||
{{-- DESKTOP TABLE --}}
|
||||
<div class="lk-tbl-wrap">
|
||||
<table class="lk-tbl">
|
||||
<thead>
|
||||
<tr>
|
||||
<th style="width:32px;">#</th>
|
||||
<th>#</th>
|
||||
<th>Nama</th>
|
||||
<th>NIK</th>
|
||||
<th>RT</th>
|
||||
|
|
@ -137,113 +130,149 @@
|
|||
<tbody>
|
||||
@forelse($laporans as $i => $item)
|
||||
@php
|
||||
$prob = $item->probability ?? 0;
|
||||
if ($prob <= 1) $prob = $prob * 100;
|
||||
|
||||
$prob = $item->probability ?? 0;
|
||||
if($prob <= 1) $prob = $prob * 100;
|
||||
$nama = $item->nama_lengkap ?? '-';
|
||||
$init = strtoupper(substr($nama, 0, 1));
|
||||
$sc = $prob >= 70 ? '#10b981' : ($prob >= 40 ? '#f59e0b' : '#f43f5e');
|
||||
|
||||
$init = strtoupper(substr($nama,0,1));
|
||||
$sc = $prob>=70?'#10b981':($prob>=40?'#f59e0b':'#f43f5e');
|
||||
$colors = ['#3b82f6','#8b5cf6','#ec4899','#f59e0b','#10b981','#ef4444','#06b6d4'];
|
||||
$avColor = $colors[ord($init) % count($colors)];
|
||||
|
||||
$diterima = $item->status_verifikasi === 'disetujui';
|
||||
$acc = ($item->status_verifikasi === 'disetujui');
|
||||
@endphp
|
||||
<tr>
|
||||
<td style="text-align:center;font-size:10.5px;color:#94a3b8;font-weight:700;">{{ $i + 1 }}</td>
|
||||
|
||||
<td style="text-align:center;font-size:10.5px;color:#94a3b8;font-weight:700;">{{ $i+1 }}</td>
|
||||
<td>
|
||||
<div class="name-cell">
|
||||
<div style="display:flex;align-items:center;gap:8px;">
|
||||
<div class="av" style="background:{{ $avColor }};">{{ $init }}</div>
|
||||
<div>
|
||||
<div style="font-size:12.5px;font-weight:700;color:#0f172a;">{{ $nama }}</div>
|
||||
</div>
|
||||
<span style="font-size:12.5px;font-weight:700;color:#0f172a;white-space:nowrap;">{{ $nama }}</span>
|
||||
</div>
|
||||
</td>
|
||||
|
||||
<td><span class="nik-pill">{{ $item->nik ?? '-' }}</span></td>
|
||||
|
||||
<td><span class="nik-pill">{{ $item->nik??'-' }}</span></td>
|
||||
<td><span class="rt-badge">RT {{ str_pad($item->nomor_rt??'-',3,'0',STR_PAD_LEFT) }}</span></td>
|
||||
<td style="font-size:11.5px;white-space:nowrap;">{{ $item->nama_dusun??'-' }}</td>
|
||||
<td style="font-size:11.5px;">{{ $item->pekerjaan??'-' }}</td>
|
||||
<td style="font-size:12px;font-weight:600;white-space:nowrap;">Rp {{ number_format($item->penghasilan??0,0,',','.') }}</td>
|
||||
<td style="text-align:center;font-size:12px;font-weight:600;">{{ $item->jumlah_tanggungan??'-' }}</td>
|
||||
<td style="font-size:11.5px;">{{ $item->aset_kepemilikan??'-' }}</td>
|
||||
<td>
|
||||
<span class="rt-badge">
|
||||
RT {{ str_pad($item->nomor_rt ?? '-', 3, '0', STR_PAD_LEFT) }}
|
||||
</span>
|
||||
</td>
|
||||
|
||||
<td style="font-size:11.5px;white-space:nowrap;">
|
||||
{{ $item->nama_dusun ?? '-' }}
|
||||
</td>
|
||||
|
||||
<td style="font-size:11.5px;max-width:110px;">{{ $item->pekerjaan ?? '-' }}</td>
|
||||
|
||||
<td style="font-size:12px;font-weight:600;color:#0f172a;white-space:nowrap;">
|
||||
Rp {{ number_format($item->penghasilan ?? 0, 0, ',', '.') }}
|
||||
</td>
|
||||
|
||||
<td style="text-align:center;font-size:12px;font-weight:600;">
|
||||
{{ $item->jumlah_tanggungan ?? '-' }}
|
||||
</td>
|
||||
|
||||
<td style="font-size:11.5px;max-width:110px;">{{ $item->aset_kepemilikan ?? '-' }}</td>
|
||||
|
||||
<td>
|
||||
@if(strtolower($item->bantuan_lain ?? '') === 'ya')
|
||||
@if(strtolower($item->bantuan_lain??'')=='ya')
|
||||
<span style="display:inline-flex;padding:2px 8px;border-radius:20px;background:#fffbeb;color:#b45309;font-size:10px;font-weight:700;border:1px solid #fde68a;">Ya</span>
|
||||
@else
|
||||
<span style="display:inline-flex;padding:2px 8px;border-radius:20px;background:#f8fafc;color:#94a3b8;font-size:10px;font-weight:600;">Tidak</span>
|
||||
@endif
|
||||
</td>
|
||||
|
||||
<td style="text-align:center;font-size:12px;color:#374151;">{{ $item->usia ?? '-' }}</td>
|
||||
|
||||
<td style="text-align:center;font-size:12px;">{{ $item->usia??'-' }}</td>
|
||||
<td>
|
||||
<div class="prob-cell">
|
||||
<div class="prob-bg">
|
||||
<div class="prob-fill" style="width:{{ min($prob,100) }}%;background:{{ $sc }};"></div>
|
||||
</div>
|
||||
<span style="font-size:11px;font-weight:800;color:{{ $sc }};white-space:nowrap;">
|
||||
{{ number_format($prob, 1) }}%
|
||||
</span>
|
||||
<div class="prob-bg"><div class="prob-fill" style="width:{{ min($prob,100) }}%;background:{{ $sc }};"></div></div>
|
||||
<span style="font-size:11px;font-weight:800;color:{{ $sc }};white-space:nowrap;">{{ number_format($prob,1) }}%</span>
|
||||
</div>
|
||||
</td>
|
||||
|
||||
<td>
|
||||
@if($diterima)
|
||||
<span class="st-pill" style="background:#f0fdf4;color:#166534;border:1px solid #bbf7d0;">
|
||||
<span class="st-dot" style="background:#22c55e;"></span>
|
||||
Diterima
|
||||
</span>
|
||||
@if($acc)
|
||||
<span class="st-pill" style="background:#f0fdf4;color:#166534;border:1px solid #bbf7d0;"><span class="st-dot" style="background:#22c55e;"></span>Diterima</span>
|
||||
@else
|
||||
<span class="st-pill" style="background:#fff1f2;color:#be123c;border:1px solid #fecdd3;">
|
||||
<span class="st-dot" style="background:#f43f5e;"></span>
|
||||
Ditolak
|
||||
</span>
|
||||
<span class="st-pill" style="background:#fff1f2;color:#be123c;border:1px solid #fecdd3;"><span class="st-dot" style="background:#f43f5e;"></span>Ditolak</span>
|
||||
@endif
|
||||
</td>
|
||||
</tr>
|
||||
@empty
|
||||
<tr>
|
||||
<td colspan="13">
|
||||
<div class="empty-state">
|
||||
<div class="empty-icon">
|
||||
<svg fill="none" stroke="#d1d5db" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5"
|
||||
d="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z"/>
|
||||
</svg>
|
||||
</div>
|
||||
<p style="font-size:13px;font-weight:600;color:#94a3b8;">Belum ada laporan yang dikirim admin</p>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
<tr><td colspan="13">
|
||||
<div class="empty-state">
|
||||
<div class="empty-icon"><svg fill="none" stroke="#d1d5db" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" d="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z"/></svg></div>
|
||||
<p style="font-size:13px;font-weight:600;color:#94a3b8;">Belum ada laporan yang dikirim admin</p>
|
||||
</div>
|
||||
</td></tr>
|
||||
@endforelse
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<div class="tbl-foot">
|
||||
<span class="credit">SiBantuDes · Kelurahan Ngerong</span>
|
||||
{{-- MOBILE CARD LIST --}}
|
||||
<div class="mob-list">
|
||||
@forelse($laporans as $i => $item)
|
||||
@php
|
||||
$prob = $item->probability ?? 0;
|
||||
if($prob <= 1) $prob = $prob * 100;
|
||||
$nama = $item->nama_lengkap ?? '-';
|
||||
$init = strtoupper(substr($nama,0,1));
|
||||
$sc = $prob>=70?'#10b981':($prob>=40?'#f59e0b':'#f43f5e');
|
||||
$scBg = $prob>=70?'#f0fdf4':($prob>=40?'#fffbeb':'#fff1f2');
|
||||
$scBd = $prob>=70?'#bbf7d0':($prob>=40?'#fde68a':'#fecdd3');
|
||||
$colors = ['#3b82f6','#8b5cf6','#ec4899','#f59e0b','#10b981','#ef4444','#06b6d4'];
|
||||
$avColor = $colors[ord($init) % count($colors)];
|
||||
$acc = ($item->status_verifikasi === 'disetujui');
|
||||
@endphp
|
||||
<div class="mob-card">
|
||||
<div class="mob-top">
|
||||
<div class="av" style="background:{{ $avColor }};">{{ $init }}</div>
|
||||
<div style="flex:1;min-width:0;">
|
||||
<div class="mob-name">{{ $nama }}</div>
|
||||
<div style="font-size:10px;font-family:monospace;color:#94a3b8;">{{ $item->nik??'-' }}</div>
|
||||
</div>
|
||||
<span style="font-size:10px;color:#94a3b8;font-weight:700;">#{{ $i+1 }}</span>
|
||||
</div>
|
||||
|
||||
<div class="mob-rows">
|
||||
<div class="mob-kv">
|
||||
<span class="mob-k">RT</span>
|
||||
<span class="rt-badge" style="width:fit-content;">RT {{ str_pad($item->nomor_rt??'-',3,'0',STR_PAD_LEFT) }}</span>
|
||||
</div>
|
||||
<div class="mob-kv">
|
||||
<span class="mob-k">Dusun</span>
|
||||
<span class="mob-v">{{ $item->nama_dusun??'-' }}</span>
|
||||
</div>
|
||||
<div class="mob-kv">
|
||||
<span class="mob-k">Pekerjaan</span>
|
||||
<span class="mob-v">{{ $item->pekerjaan??'-' }}</span>
|
||||
</div>
|
||||
<div class="mob-kv">
|
||||
<span class="mob-k">Usia</span>
|
||||
<span class="mob-v">{{ $item->usia??'-' }} thn</span>
|
||||
</div>
|
||||
<div class="mob-kv">
|
||||
<span class="mob-k">Penghasilan</span>
|
||||
<span class="mob-v">Rp {{ number_format($item->penghasilan??0,0,',','.') }}</span>
|
||||
</div>
|
||||
<div class="mob-kv">
|
||||
<span class="mob-k">Tanggungan</span>
|
||||
<span class="mob-v">{{ $item->jumlah_tanggungan??'-' }} orang</span>
|
||||
</div>
|
||||
<div class="mob-kv">
|
||||
<span class="mob-k">Aset</span>
|
||||
<span class="mob-v">{{ $item->aset_kepemilikan??'-' }}</span>
|
||||
</div>
|
||||
<div class="mob-kv">
|
||||
<span class="mob-k">Bantuan Lain</span>
|
||||
@if(strtolower($item->bantuan_lain??'')=='ya')
|
||||
<span style="display:inline-flex;padding:2px 7px;border-radius:20px;background:#fffbeb;color:#b45309;font-size:10px;font-weight:700;border:1px solid #fde68a;width:fit-content;">Ya</span>
|
||||
@else
|
||||
<span style="display:inline-flex;padding:2px 7px;border-radius:20px;background:#f8fafc;color:#94a3b8;font-size:10px;font-weight:600;width:fit-content;">Tidak</span>
|
||||
@endif
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mob-bottom">
|
||||
<div class="prob-cell">
|
||||
<div class="prob-bg"><div class="prob-fill" style="width:{{ min($prob,100) }}%;background:{{ $sc }};"></div></div>
|
||||
<span style="font-size:11px;font-weight:800;color:{{ $sc }};">{{ number_format($prob,1) }}%</span>
|
||||
</div>
|
||||
@if($acc)
|
||||
<span class="st-pill" style="background:#f0fdf4;color:#166534;border:1px solid #bbf7d0;"><span class="st-dot" style="background:#22c55e;"></span>Diterima</span>
|
||||
@else
|
||||
<span class="st-pill" style="background:#fff1f2;color:#be123c;border:1px solid #fecdd3;"><span class="st-dot" style="background:#f43f5e;"></span>Ditolak</span>
|
||||
@endif
|
||||
</div>
|
||||
</div>
|
||||
@empty
|
||||
<div class="empty-state">
|
||||
<div class="empty-icon"><svg fill="none" stroke="#d1d5db" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" d="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z"/></svg></div>
|
||||
<p style="font-size:13px;font-weight:600;color:#94a3b8;">Belum ada laporan yang dikirim admin</p>
|
||||
</div>
|
||||
@endforelse
|
||||
</div>
|
||||
|
||||
<div class="tbl-foot"><span class="credit">SiBantuDes · Kelurahan Ngerong</span></div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
</x-app-layout>
|
||||
|
|
@ -6,7 +6,6 @@
|
|||
use App\Http\Controllers\Auth\EmailVerificationPromptController;
|
||||
use App\Http\Controllers\Auth\NewPasswordController;
|
||||
use App\Http\Controllers\Auth\PasswordController;
|
||||
use App\Http\Controllers\Auth\PasswordResetLinkController;
|
||||
use App\Http\Controllers\Auth\RegisteredUserController;
|
||||
use App\Http\Controllers\Auth\VerifyEmailController;
|
||||
use Illuminate\Support\Facades\Route;
|
||||
|
|
@ -22,17 +21,8 @@
|
|||
|
||||
Route::post('login', [AuthenticatedSessionController::class, 'store']);
|
||||
|
||||
Route::get('forgot-password', [PasswordResetLinkController::class, 'create'])
|
||||
->name('password.request');
|
||||
|
||||
Route::post('forgot-password', [PasswordResetLinkController::class, 'store'])
|
||||
->name('password.email');
|
||||
|
||||
Route::get('reset-password/{token}', [NewPasswordController::class, 'create'])
|
||||
->name('password.reset');
|
||||
|
||||
Route::post('reset-password', [NewPasswordController::class, 'store'])
|
||||
->name('password.store');
|
||||
/* forgot-password & reset-password dihapus dari sini,
|
||||
sudah diganti dengan ManualPasswordResetController di web.php */
|
||||
});
|
||||
|
||||
Route::middleware('auth')->group(function () {
|
||||
|
|
@ -56,4 +46,4 @@
|
|||
|
||||
Route::post('logout', [AuthenticatedSessionController::class, 'destroy'])
|
||||
->name('logout');
|
||||
});
|
||||
});
|
||||
|
|
@ -18,6 +18,9 @@
|
|||
use App\Http\Controllers\Rt\CalonPenerimaController as RtCalonPenerimaController;
|
||||
use App\Http\Controllers\Rt\LaporanController as RtLaporanController;
|
||||
|
||||
// Manual Password Reset
|
||||
use App\Http\Controllers\Auth\ManualPasswordResetController;
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| ROOT / LANDING PAGE PUBLIK
|
||||
|
|
@ -25,6 +28,23 @@
|
|||
*/
|
||||
Route::get('/', [LandingPageController::class, 'index'])->name('landing');
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| MANUAL PASSWORD RESET (guest only)
|
||||
|--------------------------------------------------------------------------
|
||||
*/
|
||||
Route::get('/forgot-password', [ManualPasswordResetController::class, 'showForm'])
|
||||
->middleware('guest')
|
||||
->name('password.request');
|
||||
|
||||
Route::post('/password/manual/check', [ManualPasswordResetController::class, 'checkEmail'])
|
||||
->middleware('guest')
|
||||
->name('password.manual.check');
|
||||
|
||||
Route::post('/password/manual/update', [ManualPasswordResetController::class, 'updatePassword'])
|
||||
->middleware('guest')
|
||||
->name('password.manual.update');
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| DASHBOARD UTAMA (redirect sesuai role)
|
||||
|
|
|
|||
Loading…
Reference in New Issue