make landing page, fix blade admin & RT
This commit is contained in:
parent
b0140be0a8
commit
e55dc1104b
|
|
@ -0,0 +1,35 @@
|
||||||
|
FROM php:8.2-apache
|
||||||
|
|
||||||
|
RUN apt-get update && apt-get install -y \
|
||||||
|
git \
|
||||||
|
unzip \
|
||||||
|
libpq-dev \
|
||||||
|
libzip-dev \
|
||||||
|
zip \
|
||||||
|
curl \
|
||||||
|
python3 \
|
||||||
|
python3-pip \
|
||||||
|
&& docker-php-ext-install pdo pdo_pgsql pgsql zip
|
||||||
|
|
||||||
|
COPY --from=composer:2 /usr/bin/composer /usr/bin/composer
|
||||||
|
|
||||||
|
WORKDIR /var/www/html
|
||||||
|
|
||||||
|
COPY . /var/www/html
|
||||||
|
|
||||||
|
RUN composer install --no-dev --optimize-autoloader
|
||||||
|
|
||||||
|
RUN if [ -f requirements.txt ]; then pip3 install -r requirements.txt; fi
|
||||||
|
|
||||||
|
RUN a2enmod rewrite
|
||||||
|
|
||||||
|
RUN chown -R www-data:www-data /var/www/html \
|
||||||
|
&& chmod -R 775 /var/www/html/storage /var/www/html/bootstrap/cache
|
||||||
|
|
||||||
|
COPY render-apache.conf /etc/apache2/sites-available/000-default.conf
|
||||||
|
|
||||||
|
EXPOSE 80
|
||||||
|
|
||||||
|
CMD php artisan config:clear && \
|
||||||
|
php artisan cache:clear && \
|
||||||
|
apache2-foreground
|
||||||
|
|
@ -0,0 +1,319 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Exports;
|
||||||
|
|
||||||
|
use App\Models\PenerimaFinal;
|
||||||
|
use Maatwebsite\Excel\Concerns\FromCollection;
|
||||||
|
use Maatwebsite\Excel\Concerns\WithHeadings;
|
||||||
|
use Maatwebsite\Excel\Concerns\WithStyles;
|
||||||
|
use Maatwebsite\Excel\Concerns\WithColumnWidths;
|
||||||
|
use Maatwebsite\Excel\Concerns\WithTitle;
|
||||||
|
use Maatwebsite\Excel\Concerns\WithCustomStartCell;
|
||||||
|
use Maatwebsite\Excel\Concerns\WithEvents;
|
||||||
|
use Maatwebsite\Excel\Events\AfterSheet;
|
||||||
|
use PhpOffice\PhpSpreadsheet\Worksheet\Worksheet;
|
||||||
|
use PhpOffice\PhpSpreadsheet\Worksheet\PageSetup;
|
||||||
|
use PhpOffice\PhpSpreadsheet\Style\Fill;
|
||||||
|
use PhpOffice\PhpSpreadsheet\Style\Alignment;
|
||||||
|
use PhpOffice\PhpSpreadsheet\Style\Border;
|
||||||
|
|
||||||
|
class PenerimaFinalExport implements
|
||||||
|
FromCollection,
|
||||||
|
WithHeadings,
|
||||||
|
WithStyles,
|
||||||
|
WithColumnWidths,
|
||||||
|
WithTitle,
|
||||||
|
WithCustomStartCell,
|
||||||
|
WithEvents
|
||||||
|
{
|
||||||
|
protected int $rowCount = 0;
|
||||||
|
|
||||||
|
// ── Collection ────────────────────────────────────────────────────────────
|
||||||
|
public function collection()
|
||||||
|
{
|
||||||
|
$items = PenerimaFinal::query()
|
||||||
|
->where('status_verifikasi', 'disetujui')
|
||||||
|
->orderByDesc('tanggal_penetapan')
|
||||||
|
->get();
|
||||||
|
|
||||||
|
$this->rowCount = $items->count();
|
||||||
|
|
||||||
|
return $items->map(function ($item, $index) {
|
||||||
|
$prob = $item->probability ?? 0;
|
||||||
|
|
||||||
|
if ($prob <= 1) {
|
||||||
|
$prob = $prob * 100;
|
||||||
|
}
|
||||||
|
|
||||||
|
$nama = $item->nama_lengkap ?? $item->nama ?? '-';
|
||||||
|
|
||||||
|
// Penghasilan: coba dari relasi calonPenerima, fallback ke kolom langsung.
|
||||||
|
// Jika null atau 0, tampilkan teks supaya cell tidak kosong.
|
||||||
|
$penghasilanRaw = $item->calonPenerima?->penghasilan
|
||||||
|
?? $item->penghasilan
|
||||||
|
?? null;
|
||||||
|
|
||||||
|
$penghasilan = ($penghasilanRaw !== null && (int) $penghasilanRaw > 0)
|
||||||
|
? (int) $penghasilanRaw
|
||||||
|
: 'Tidak Berpenghasilan';
|
||||||
|
|
||||||
|
return [
|
||||||
|
'No' => $index + 1,
|
||||||
|
'Nama Lengkap' => $nama,
|
||||||
|
'NIK' => "\t" . ($item->nik ?? '-'),
|
||||||
|
'No KK' => "\t" . ($item->no_kk ?? '-'),
|
||||||
|
'Nomor RT' => $item->nomor_rt ?? '-',
|
||||||
|
'Dusun' => $item->nama_dusun ?? '-',
|
||||||
|
'Pekerjaan' => $item->pekerjaan ?? '-',
|
||||||
|
'Penghasilan' => $penghasilan,
|
||||||
|
'Tanggungan' => $item->jumlah_tanggungan ?? 0,
|
||||||
|
'Aset' => $item->aset_kepemilikan ?? '-',
|
||||||
|
'Bantuan Lain' => ucfirst($item->bantuan_lain ?? 'tidak'),
|
||||||
|
'Usia' => $item->usia ?? 0,
|
||||||
|
'Probabilitas' => round($prob, 1),
|
||||||
|
'Status' => ucfirst($item->status_verifikasi ?? '-'),
|
||||||
|
'Tgl Penetapan' => optional($item->tanggal_penetapan)->format('d-m-Y') ?? '-',
|
||||||
|
'Periode Bantuan' => $item->periode_bantuan ?? '-',
|
||||||
|
'Jumlah Bantuan' => $item->jumlah_bantuan ?? 300000,
|
||||||
|
];
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Headings ──────────────────────────────────────────────────────────────
|
||||||
|
public function headings(): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
'No', 'Nama Lengkap', 'NIK', 'No KK', 'Nomor RT', 'Dusun',
|
||||||
|
'Pekerjaan', 'Penghasilan (Rp)', 'Tanggungan',
|
||||||
|
'Aset Kepemilikan', 'Bantuan Lain', 'Usia',
|
||||||
|
'Probabilitas (%)', 'Status',
|
||||||
|
'Tgl Penetapan', 'Periode Bantuan', 'Jumlah Bantuan (Rp)',
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
public function startCell(): string { return 'A5'; }
|
||||||
|
public function title(): string { return 'Penerima BLT-DD'; }
|
||||||
|
|
||||||
|
// ── Lebar kolom ──────────────────────────────────────────────────────────
|
||||||
|
public function columnWidths(): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
'A' => 5, 'B' => 28, 'C' => 20, 'D' => 20,
|
||||||
|
'E' => 8, 'F' => 18, 'G' => 20, 'H' => 18,
|
||||||
|
'I' => 9, 'J' => 22, 'K' => 13, 'L' => 7,
|
||||||
|
'M' => 14, 'N' => 16, 'O' => 15, 'P' => 18, 'Q' => 20,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Style header tabel (baris 5) ─────────────────────────────────────────
|
||||||
|
public function styles(Worksheet $sheet)
|
||||||
|
{
|
||||||
|
$sheet->getStyle('A5:Q5')->applyFromArray([
|
||||||
|
'font' => [
|
||||||
|
'bold' => true,
|
||||||
|
'color' => ['argb' => 'FFFFFFFF'],
|
||||||
|
'size' => 9,
|
||||||
|
'name' => 'Arial',
|
||||||
|
],
|
||||||
|
'fill' => [
|
||||||
|
'fillType' => Fill::FILL_SOLID,
|
||||||
|
'startColor' => ['argb' => 'FF1E3A8A'],
|
||||||
|
],
|
||||||
|
'alignment' => [
|
||||||
|
'horizontal' => Alignment::HORIZONTAL_CENTER,
|
||||||
|
'vertical' => Alignment::VERTICAL_CENTER,
|
||||||
|
'wrapText' => true,
|
||||||
|
],
|
||||||
|
]);
|
||||||
|
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Events ────────────────────────────────────────────────────────────────
|
||||||
|
public function registerEvents(): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
AfterSheet::class => function (AfterSheet $event) {
|
||||||
|
$sheet = $event->sheet->getDelegate();
|
||||||
|
$lastRow = 5 + $this->rowCount;
|
||||||
|
|
||||||
|
$this->buildTitleBlock($sheet);
|
||||||
|
$this->formatDataRows($sheet, $lastRow);
|
||||||
|
$this->applyTableBorders($sheet, $lastRow);
|
||||||
|
$this->buildSummaryRow($sheet, $lastRow);
|
||||||
|
$this->buildFootnote($sheet, $lastRow);
|
||||||
|
$this->applyPrintSettings($sheet);
|
||||||
|
|
||||||
|
$sheet->freezePane('A6');
|
||||||
|
$sheet->setAutoFilter("A5:Q5");
|
||||||
|
},
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Helpers ───────────────────────────────────────────────────────────────
|
||||||
|
private function buildTitleBlock(Worksheet $sheet): void
|
||||||
|
{
|
||||||
|
foreach (['A1:Q1', 'A2:Q2', 'A3:Q3', 'A4:Q4'] as $range) {
|
||||||
|
$sheet->mergeCells($range);
|
||||||
|
}
|
||||||
|
|
||||||
|
$sheet->setCellValue('A1', 'LAPORAN PENERIMA BANTUAN LANGSUNG TUNAI DANA DESA (BLT-DD)');
|
||||||
|
$sheet->setCellValue('A2', 'Desa Ngerong, Kecamatan Gempol, Kabupaten Pasuruan — Jawa Timur');
|
||||||
|
$sheet->setCellValue('A3', 'Status: Disetujui | Dicetak: ' . now()->translatedFormat('d F Y, H:i') . ' WIB');
|
||||||
|
$sheet->setCellValue('A4', '');
|
||||||
|
|
||||||
|
$sheet->getStyle('A1')->applyFromArray([
|
||||||
|
'font' => ['bold' => true, 'size' => 14, 'name' => 'Arial', 'color' => ['argb' => 'FF1E3A8A']],
|
||||||
|
'alignment' => ['horizontal' => Alignment::HORIZONTAL_CENTER, 'vertical' => Alignment::VERTICAL_CENTER],
|
||||||
|
]);
|
||||||
|
$sheet->getStyle('A2')->applyFromArray([
|
||||||
|
'font' => ['size' => 10, 'name' => 'Arial', 'color' => ['argb' => 'FF334155']],
|
||||||
|
'alignment' => ['horizontal' => Alignment::HORIZONTAL_CENTER],
|
||||||
|
]);
|
||||||
|
$sheet->getStyle('A3')->applyFromArray([
|
||||||
|
'font' => ['size' => 9, 'italic' => true, 'name' => 'Arial', 'color' => ['argb' => 'FF64748B']],
|
||||||
|
'alignment' => ['horizontal' => Alignment::HORIZONTAL_CENTER],
|
||||||
|
]);
|
||||||
|
$sheet->getStyle('A3:Q3')->applyFromArray([
|
||||||
|
'borders' => [
|
||||||
|
'bottom' => ['borderStyle' => Border::BORDER_MEDIUM, 'color' => ['argb' => 'FF1E3A8A']],
|
||||||
|
],
|
||||||
|
]);
|
||||||
|
|
||||||
|
$sheet->getRowDimension(1)->setRowHeight(26);
|
||||||
|
$sheet->getRowDimension(2)->setRowHeight(16);
|
||||||
|
$sheet->getRowDimension(3)->setRowHeight(14);
|
||||||
|
$sheet->getRowDimension(4)->setRowHeight(6);
|
||||||
|
$sheet->getRowDimension(5)->setRowHeight(30);
|
||||||
|
}
|
||||||
|
|
||||||
|
private function formatDataRows(Worksheet $sheet, int $lastRow): void
|
||||||
|
{
|
||||||
|
for ($row = 6; $row <= $lastRow; $row++) {
|
||||||
|
$isEven = ($row % 2 === 0);
|
||||||
|
|
||||||
|
$sheet->getStyle("A{$row}:Q{$row}")->applyFromArray([
|
||||||
|
'fill' => [
|
||||||
|
'fillType' => Fill::FILL_SOLID,
|
||||||
|
'startColor' => ['argb' => $isEven ? 'FFF8FAFC' : 'FFFFFFFF'],
|
||||||
|
],
|
||||||
|
'font' => ['name' => 'Arial', 'size' => 9],
|
||||||
|
'alignment' => ['vertical' => Alignment::VERTICAL_CENTER],
|
||||||
|
]);
|
||||||
|
|
||||||
|
$sheet->getRowDimension($row)->setRowHeight(18);
|
||||||
|
|
||||||
|
foreach (['A', 'E', 'I', 'L', 'M', 'N', 'O'] as $col) {
|
||||||
|
$sheet->getStyle("{$col}{$row}")
|
||||||
|
->getAlignment()->setHorizontal(Alignment::HORIZONTAL_CENTER);
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (['H', 'Q'] as $col) {
|
||||||
|
$sheet->getStyle("{$col}{$row}")
|
||||||
|
->getAlignment()->setHorizontal(Alignment::HORIZONTAL_RIGHT);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Format Penghasilan — angka pakai Rp, teks (Tidak Berpenghasilan) pakai italic gray
|
||||||
|
$hVal = $sheet->getCell("H{$row}")->getValue();
|
||||||
|
if (is_numeric($hVal)) {
|
||||||
|
$sheet->getStyle("H{$row}")->applyFromArray([
|
||||||
|
'numberFormat' => ['formatCode' => '"Rp "#,##0'],
|
||||||
|
'alignment' => ['horizontal' => Alignment::HORIZONTAL_RIGHT],
|
||||||
|
]);
|
||||||
|
} else {
|
||||||
|
$sheet->getStyle("H{$row}")->applyFromArray([
|
||||||
|
'font' => ['italic' => true, 'color' => ['argb' => 'FF94A3B8'], 'size' => 8, 'name' => 'Arial'],
|
||||||
|
'alignment' => ['horizontal' => Alignment::HORIZONTAL_CENTER],
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
$sheet->getStyle("Q{$row}")->applyFromArray([
|
||||||
|
'font' => ['bold' => true, 'color' => ['argb' => 'FF166534'], 'name' => 'Arial', 'size' => 9],
|
||||||
|
'numberFormat' => ['formatCode' => '"Rp "#,##0'],
|
||||||
|
]);
|
||||||
|
|
||||||
|
$prob = (float) ($sheet->getCell("M{$row}")->getValue() ?? 0);
|
||||||
|
$probColor = $prob >= 70 ? 'FF10B981' : ($prob >= 40 ? 'FFF59E0B' : 'FFF43F5E');
|
||||||
|
$sheet->getStyle("M{$row}")->applyFromArray([
|
||||||
|
'font' => ['bold' => true, 'color' => ['argb' => $probColor], 'name' => 'Arial', 'size' => 9],
|
||||||
|
'alignment' => ['horizontal' => Alignment::HORIZONTAL_CENTER],
|
||||||
|
'numberFormat' => ['formatCode' => '0.0"%"'],
|
||||||
|
]);
|
||||||
|
|
||||||
|
$sheet->getStyle("N{$row}")->applyFromArray([
|
||||||
|
'font' => ['bold' => true, 'color' => ['argb' => 'FF166534'], 'name' => 'Arial', 'size' => 9],
|
||||||
|
'alignment' => ['horizontal' => Alignment::HORIZONTAL_CENTER],
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private function applyTableBorders(Worksheet $sheet, int $lastRow): void
|
||||||
|
{
|
||||||
|
$sheet->getStyle("A5:Q{$lastRow}")->applyFromArray([
|
||||||
|
'borders' => [
|
||||||
|
'allBorders' => [
|
||||||
|
'borderStyle' => Border::BORDER_THIN,
|
||||||
|
'color' => ['argb' => 'FFE2E8F0'],
|
||||||
|
],
|
||||||
|
'outline' => [
|
||||||
|
'borderStyle' => Border::BORDER_MEDIUM,
|
||||||
|
'color' => ['argb' => 'FF1E3A8A'],
|
||||||
|
],
|
||||||
|
],
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
private function buildSummaryRow(Worksheet $sheet, int $lastRow): void
|
||||||
|
{
|
||||||
|
$sumRow = $lastRow + 2;
|
||||||
|
|
||||||
|
$sheet->mergeCells("A{$sumRow}:B{$sumRow}");
|
||||||
|
$sheet->setCellValue("A{$sumRow}", 'RINGKASAN');
|
||||||
|
$sheet->setCellValue("C{$sumRow}", 'Total Penerima:');
|
||||||
|
$sheet->setCellValue("D{$sumRow}", $this->rowCount . ' orang');
|
||||||
|
// ← Total Penghasilan dihapus, diganti Total Dana BLT saja
|
||||||
|
$sheet->setCellValue("P{$sumRow}", 'Total Dana BLT:');
|
||||||
|
$sheet->setCellValue("Q{$sumRow}", "=SUM(Q6:Q{$lastRow})");
|
||||||
|
|
||||||
|
$sheet->getStyle("A{$sumRow}:Q{$sumRow}")->applyFromArray([
|
||||||
|
'font' => ['bold' => true, 'size' => 9, 'name' => 'Arial', 'color' => ['argb' => 'FF1E3A8A']],
|
||||||
|
'fill' => ['fillType' => Fill::FILL_SOLID, 'startColor' => ['argb' => 'FFEFF6FF']],
|
||||||
|
'borders' => [
|
||||||
|
'outline' => ['borderStyle' => Border::BORDER_MEDIUM, 'color' => ['argb' => 'FF1E3A8A']],
|
||||||
|
],
|
||||||
|
]);
|
||||||
|
|
||||||
|
$sheet->getStyle("Q{$sumRow}")->getNumberFormat()->setFormatCode('"Rp "#,##0');
|
||||||
|
$sheet->getRowDimension($sumRow)->setRowHeight(18);
|
||||||
|
}
|
||||||
|
|
||||||
|
private function buildFootnote(Worksheet $sheet, int $lastRow): void
|
||||||
|
{
|
||||||
|
$noteRow = $lastRow + 4;
|
||||||
|
$sheet->mergeCells("A{$noteRow}:Q{$noteRow}");
|
||||||
|
$sheet->setCellValue(
|
||||||
|
"A{$noteRow}",
|
||||||
|
'* Dokumen ini digenerate otomatis oleh SiBantuDes — Sistem Informasi BLT Dana Desa, Desa Ngerong, Kab. Pasuruan.'
|
||||||
|
);
|
||||||
|
$sheet->getStyle("A{$noteRow}")->applyFromArray([
|
||||||
|
'font' => ['italic' => true, 'size' => 8, 'name' => 'Arial', 'color' => ['argb' => 'FF94A3B8']],
|
||||||
|
'alignment' => ['horizontal' => Alignment::HORIZONTAL_LEFT],
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
private function applyPrintSettings(Worksheet $sheet): void
|
||||||
|
{
|
||||||
|
$sheet->getPageSetup()
|
||||||
|
->setOrientation(PageSetup::ORIENTATION_LANDSCAPE)
|
||||||
|
->setPaperSize(PageSetup::PAPERSIZE_A4)
|
||||||
|
->setFitToWidth(1)
|
||||||
|
->setFitToHeight(0);
|
||||||
|
|
||||||
|
$sheet->getPageMargins()
|
||||||
|
->setTop(0.75)->setBottom(0.75)
|
||||||
|
->setLeft(0.5)->setRight(0.5);
|
||||||
|
|
||||||
|
$sheet->getHeaderFooter()
|
||||||
|
->setOddHeader('&C&B Laporan Penerima BLT-DD — Desa Ngerong')
|
||||||
|
->setOddFooter('&L&8SiBantuDes&C&8Halaman &P dari &N&R&8' . now()->format('d/m/Y'));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -14,6 +14,7 @@ class FilterisasiController extends Controller
|
||||||
public function index(Request $request)
|
public function index(Request $request)
|
||||||
{
|
{
|
||||||
$user = $request->user();
|
$user = $request->user();
|
||||||
|
|
||||||
if (!$user || $user->role !== 'admin') {
|
if (!$user || $user->role !== 'admin') {
|
||||||
abort(403, 'Akses ditolak: hanya Admin.');
|
abort(403, 'Akses ditolak: hanya Admin.');
|
||||||
}
|
}
|
||||||
|
|
@ -28,9 +29,11 @@ public function index(Request $request)
|
||||||
}
|
}
|
||||||
|
|
||||||
$candidates = collect();
|
$candidates = collect();
|
||||||
$pickedIds = collect();
|
$pickedNiks = collect();
|
||||||
|
|
||||||
if ($dusunId > 0) {
|
if ($dusunId > 0) {
|
||||||
|
$dusun = Dusun::find($dusunId);
|
||||||
|
|
||||||
$query = CalonPenerima::query()
|
$query = CalonPenerima::query()
|
||||||
->with(['rt.dusun', 'prediksiKelayakan'])
|
->with(['rt.dusun', 'prediksiKelayakan'])
|
||||||
->whereIn('tracking_status', ['sedang_validasi', 'selesai'])
|
->whereIn('tracking_status', ['sedang_validasi', 'selesai'])
|
||||||
|
|
@ -45,16 +48,19 @@ public function index(Request $request)
|
||||||
|
|
||||||
$candidates = $query->paginate(15)->withQueryString();
|
$candidates = $query->paginate(15)->withQueryString();
|
||||||
|
|
||||||
$pickedIds = PenerimaFinal::whereIn('calon_penerima_id', $candidates->pluck('id')->all())
|
$pickedNiks = PenerimaFinal::query()
|
||||||
->pluck('calon_penerima_id');
|
->where('status_verifikasi', 'disetujui')
|
||||||
|
->where('nama_dusun', $dusun?->nama_dusun)
|
||||||
|
->pluck('nik');
|
||||||
}
|
}
|
||||||
|
|
||||||
return view('admin.filterisasi', compact('dusuns', 'dusunId', 'kuota', 'candidates', 'pickedIds'));
|
return view('admin.filterisasi', compact('dusuns', 'dusunId', 'kuota', 'candidates', 'pickedNiks'));
|
||||||
}
|
}
|
||||||
|
|
||||||
public function tetapkan(Request $request)
|
public function tetapkan(Request $request)
|
||||||
{
|
{
|
||||||
$user = $request->user();
|
$user = $request->user();
|
||||||
|
|
||||||
if (!$user || $user->role !== 'admin') {
|
if (!$user || $user->role !== 'admin') {
|
||||||
abort(403, 'Akses ditolak: hanya Admin.');
|
abort(403, 'Akses ditolak: hanya Admin.');
|
||||||
}
|
}
|
||||||
|
|
@ -68,14 +74,16 @@ public function tetapkan(Request $request)
|
||||||
$kuota = (int) ($data['kuota'] ?? 7);
|
$kuota = (int) ($data['kuota'] ?? 7);
|
||||||
|
|
||||||
DB::transaction(function () use ($dusunId, $kuota) {
|
DB::transaction(function () use ($dusunId, $kuota) {
|
||||||
// Ambil semua kandidat dalam dusun yang sedang divalidasi / sudah selesai
|
$dusun = Dusun::findOrFail($dusunId);
|
||||||
|
|
||||||
$allCandidates = CalonPenerima::query()
|
$allCandidates = CalonPenerima::query()
|
||||||
|
->with(['rt.dusun', 'prediksiKelayakan'])
|
||||||
->whereIn('tracking_status', ['sedang_validasi', 'selesai'])
|
->whereIn('tracking_status', ['sedang_validasi', 'selesai'])
|
||||||
->whereHas('rt', function ($r) use ($dusunId) {
|
->whereHas('rt', function ($r) use ($dusunId) {
|
||||||
$r->where('dusun_id', $dusunId);
|
$r->where('dusun_id', $dusunId);
|
||||||
})
|
})
|
||||||
->leftJoin('prediksi_kelayakans', 'prediksi_kelayakans.calon_penerima_id', '=', 'calon_penerimas.id')
|
->leftJoin('prediksi_kelayakans', 'prediksi_kelayakans.calon_penerima_id', '=', 'calon_penerimas.id')
|
||||||
->select('calon_penerimas.id', DB::raw('COALESCE(prediksi_kelayakans.probability, 0) as prob'))
|
->select('calon_penerimas.*', DB::raw('COALESCE(prediksi_kelayakans.probability, 0) as prob'))
|
||||||
->orderByDesc('prob')
|
->orderByDesc('prob')
|
||||||
->orderByDesc('calon_penerimas.created_at')
|
->orderByDesc('calon_penerimas.created_at')
|
||||||
->get();
|
->get();
|
||||||
|
|
@ -83,39 +91,56 @@ public function tetapkan(Request $request)
|
||||||
$topIds = $allCandidates->take($kuota)->pluck('id')->all();
|
$topIds = $allCandidates->take($kuota)->pluck('id')->all();
|
||||||
$allIds = $allCandidates->pluck('id')->all();
|
$allIds = $allCandidates->pluck('id')->all();
|
||||||
|
|
||||||
// Hapus hasil final lama untuk kandidat pada dusun ini
|
// Top kuota = disetujui
|
||||||
if (!empty($allIds)) {
|
|
||||||
PenerimaFinal::whereIn('calon_penerima_id', $allIds)->delete();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Simpan hasil final penerima yang masuk kuota
|
|
||||||
foreach ($topIds as $id) {
|
|
||||||
PenerimaFinal::updateOrCreate(
|
|
||||||
['calon_penerima_id' => $id],
|
|
||||||
[
|
|
||||||
'tanggal_penetapan' => now()->toDateString(),
|
|
||||||
'periode_bantuan' => date('Y') . ' Triwulan 1',
|
|
||||||
'jumlah_bantuan' => 0,
|
|
||||||
'status_pencairan' => 'belum_cair',
|
|
||||||
'tanggal_pencairan' => null,
|
|
||||||
]
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Yang masuk kuota = disetujui
|
|
||||||
if (!empty($topIds)) {
|
if (!empty($topIds)) {
|
||||||
CalonPenerima::whereIn('id', $topIds)->update([
|
CalonPenerima::whereIn('id', $topIds)->update([
|
||||||
'status_verifikasi' => 'disetujui',
|
'status_verifikasi' => 'disetujui',
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Yang tidak masuk kuota = ditolak
|
// Sisanya = ditolak
|
||||||
$notPickedIds = array_diff($allIds, $topIds);
|
$notPickedIds = array_diff($allIds, $topIds);
|
||||||
|
|
||||||
if (!empty($notPickedIds)) {
|
if (!empty($notPickedIds)) {
|
||||||
CalonPenerima::whereIn('id', $notPickedIds)->update([
|
CalonPenerima::whereIn('id', $notPickedIds)->update([
|
||||||
'status_verifikasi' => 'ditolak',
|
'status_verifikasi' => 'ditolak',
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Ambil ulang data final setelah status diperbarui
|
||||||
|
$finalCandidates = CalonPenerima::query()
|
||||||
|
->with(['rt.dusun', 'prediksiKelayakan'])
|
||||||
|
->whereIn('id', $allIds)
|
||||||
|
->get();
|
||||||
|
|
||||||
|
foreach ($finalCandidates as $candidate) {
|
||||||
|
$probability = $candidate->prediksiKelayakan->probability ?? 0;
|
||||||
|
|
||||||
|
PenerimaFinal::updateOrCreate(
|
||||||
|
[
|
||||||
|
'nik' => $candidate->nik,
|
||||||
|
'periode_bantuan' => '2026 Triwulan 1',
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'nama_lengkap' => $candidate->nama_lengkap ?? $candidate->nama ?? '-',
|
||||||
|
'no_kk' => $candidate->no_kk ?? '-',
|
||||||
|
'rt_id' => $candidate->rt_id,
|
||||||
|
'nomor_rt' => $candidate->rt?->nomor_rt ?? null,
|
||||||
|
'nama_dusun' => $candidate->rt?->dusun?->nama_dusun ?? $dusun->nama_dusun,
|
||||||
|
'pekerjaan' => $candidate->pekerjaan ?? '-',
|
||||||
|
'penghasilan' => $candidate->penghasilan ?? 0,
|
||||||
|
'jumlah_tanggungan' => $candidate->jumlah_tanggungan ?? 0,
|
||||||
|
'aset_kepemilikan' => $candidate->aset_kepemilikan ?? '-',
|
||||||
|
'bantuan_lain' => $candidate->bantuan_lain ?? 'tidak',
|
||||||
|
'usia' => $candidate->usia ?? 0,
|
||||||
|
'probability' => $probability,
|
||||||
|
'status_verifikasi' => $candidate->status_verifikasi ?? 'pending',
|
||||||
|
'tanggal_penetapan' => now()->toDateString(),
|
||||||
|
'periode_bantuan' => '2026 Triwulan 1',
|
||||||
|
'jumlah_bantuan' => 300000,
|
||||||
|
]
|
||||||
|
);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
return back()->with('success', "Berhasil menetapkan kuota {$kuota} penerima untuk dusun terpilih.");
|
return back()->with('success', "Berhasil menetapkan kuota {$kuota} penerima untuk dusun terpilih.");
|
||||||
|
|
@ -124,6 +149,7 @@ public function tetapkan(Request $request)
|
||||||
public function resetDusun(Request $request)
|
public function resetDusun(Request $request)
|
||||||
{
|
{
|
||||||
$user = $request->user();
|
$user = $request->user();
|
||||||
|
|
||||||
if (!$user || $user->role !== 'admin') {
|
if (!$user || $user->role !== 'admin') {
|
||||||
abort(403, 'Akses ditolak: hanya Admin.');
|
abort(403, 'Akses ditolak: hanya Admin.');
|
||||||
}
|
}
|
||||||
|
|
@ -132,24 +158,24 @@ public function resetDusun(Request $request)
|
||||||
'dusun_id' => 'required|exists:dusuns,id',
|
'dusun_id' => 'required|exists:dusuns,id',
|
||||||
]);
|
]);
|
||||||
|
|
||||||
$dusunId = (int) $data['dusun_id'];
|
$dusun = Dusun::findOrFail($data['dusun_id']);
|
||||||
|
|
||||||
DB::transaction(function () use ($dusunId) {
|
DB::transaction(function () use ($dusun) {
|
||||||
$candidateIds = CalonPenerima::query()
|
$candidateIds = CalonPenerima::query()
|
||||||
->whereHas('rt', function ($r) use ($dusunId) {
|
->whereHas('rt', function ($r) use ($dusun) {
|
||||||
$r->where('dusun_id', $dusunId);
|
$r->where('dusun_id', $dusun->id);
|
||||||
})
|
})
|
||||||
->whereIn('tracking_status', ['sedang_validasi', 'selesai'])
|
->whereIn('tracking_status', ['sedang_validasi', 'selesai'])
|
||||||
->pluck('id')
|
->pluck('id')
|
||||||
->all();
|
->all();
|
||||||
|
|
||||||
if (!empty($candidateIds)) {
|
if (!empty($candidateIds)) {
|
||||||
PenerimaFinal::whereIn('calon_penerima_id', $candidateIds)->delete();
|
|
||||||
|
|
||||||
CalonPenerima::whereIn('id', $candidateIds)->update([
|
CalonPenerima::whereIn('id', $candidateIds)->update([
|
||||||
'status_verifikasi' => 'pending',
|
'status_verifikasi' => 'pending',
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
PenerimaFinal::where('nama_dusun', $dusun->nama_dusun)->delete();
|
||||||
});
|
});
|
||||||
|
|
||||||
return back()->with('success', 'Hasil filterisasi dusun berhasil di-reset.');
|
return back()->with('success', 'Hasil filterisasi dusun berhasil di-reset.');
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,146 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http\Controllers\Admin;
|
||||||
|
|
||||||
|
use App\Http\Controllers\Controller;
|
||||||
|
use App\Models\CalonPenerima;
|
||||||
|
use App\Models\PenerimaFinal;
|
||||||
|
use App\Models\LaporanPublik;
|
||||||
|
use Illuminate\Http\Request;
|
||||||
|
use Illuminate\Support\Facades\Storage;
|
||||||
|
use Barryvdh\DomPDF\Facade\Pdf;
|
||||||
|
use App\Exports\PenerimaFinalExport;
|
||||||
|
use Maatwebsite\Excel\Facades\Excel;
|
||||||
|
|
||||||
|
class LaporanController extends Controller
|
||||||
|
{
|
||||||
|
public function index(Request $request)
|
||||||
|
{
|
||||||
|
$user = $request->user();
|
||||||
|
|
||||||
|
if (!$user || $user->role !== 'admin') {
|
||||||
|
abort(403, 'Akses ditolak: hanya Admin.');
|
||||||
|
}
|
||||||
|
|
||||||
|
$laporans = PenerimaFinal::query()
|
||||||
|
->where('status_verifikasi', 'disetujui')
|
||||||
|
->orderByDesc('tanggal_penetapan')
|
||||||
|
->get();
|
||||||
|
|
||||||
|
$laporanPubliks = LaporanPublik::latest()->get();
|
||||||
|
|
||||||
|
return view('admin.laporan', compact('laporans', 'laporanPubliks'));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function exportPdf(Request $request)
|
||||||
|
{
|
||||||
|
$user = $request->user();
|
||||||
|
|
||||||
|
if (!$user || $user->role !== 'admin') {
|
||||||
|
abort(403, 'Akses ditolak: hanya Admin.');
|
||||||
|
}
|
||||||
|
|
||||||
|
$laporans = PenerimaFinal::query()
|
||||||
|
->where('status_verifikasi', 'disetujui')
|
||||||
|
->orderByDesc('tanggal_penetapan')
|
||||||
|
->get();
|
||||||
|
|
||||||
|
$pdf = Pdf::loadView('admin.laporan-pdf', compact('laporans'))
|
||||||
|
->setPaper('a4', 'landscape');
|
||||||
|
|
||||||
|
return $pdf->download('laporan-penerima-blt-dd-kelurahan-ngerong.pdf');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function exportExcel(Request $request)
|
||||||
|
{
|
||||||
|
$user = $request->user();
|
||||||
|
|
||||||
|
if (!$user || $user->role !== 'admin') {
|
||||||
|
abort(403, 'Akses ditolak: hanya Admin.');
|
||||||
|
}
|
||||||
|
|
||||||
|
return Excel::download(new PenerimaFinalExport, 'laporan-penerima-blt-dd-kelurahan-ngerong.xlsx');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function kirimKeRt(Request $request)
|
||||||
|
{
|
||||||
|
$user = $request->user();
|
||||||
|
|
||||||
|
if (!$user || $user->role !== 'admin') {
|
||||||
|
abort(403, 'Akses ditolak: hanya Admin.');
|
||||||
|
}
|
||||||
|
|
||||||
|
$candidateIds = CalonPenerima::query()
|
||||||
|
->whereIn('tracking_status', ['sedang_validasi'])
|
||||||
|
->whereIn('status_verifikasi', ['disetujui', 'ditolak'])
|
||||||
|
->pluck('id')
|
||||||
|
->all();
|
||||||
|
|
||||||
|
if (!empty($candidateIds)) {
|
||||||
|
CalonPenerima::whereIn('id', $candidateIds)->update([
|
||||||
|
'tracking_status' => 'selesai',
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
return back()->with('success', 'Hasil final berhasil dikirim ke RT dusun. RT sekarang hanya dapat melihat status diterima atau ditolak.');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function uploadPublikPdf(Request $request)
|
||||||
|
{
|
||||||
|
$user = $request->user();
|
||||||
|
|
||||||
|
if (!$user || $user->role !== 'admin') {
|
||||||
|
abort(403, 'Akses ditolak: hanya Admin.');
|
||||||
|
}
|
||||||
|
|
||||||
|
$data = $request->validate([
|
||||||
|
'judul' => 'required|string|max:255',
|
||||||
|
'file_pdf' => 'required|mimes:pdf|max:5120',
|
||||||
|
]);
|
||||||
|
|
||||||
|
$path = $request->file('file_pdf')->store('laporan-publik', 'public');
|
||||||
|
|
||||||
|
LaporanPublik::create([
|
||||||
|
'judul' => $data['judul'],
|
||||||
|
'file_path' => $path,
|
||||||
|
'uploaded_by' => $user->id,
|
||||||
|
'is_active' => true,
|
||||||
|
]);
|
||||||
|
|
||||||
|
return back()->with('success', 'PDF laporan publik berhasil diupload.');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function hapusPublikPdf(Request $request, $id)
|
||||||
|
{
|
||||||
|
$user = $request->user();
|
||||||
|
|
||||||
|
if (!$user || $user->role !== 'admin') {
|
||||||
|
abort(403, 'Akses ditolak: hanya Admin.');
|
||||||
|
}
|
||||||
|
|
||||||
|
$laporan = LaporanPublik::findOrFail($id);
|
||||||
|
|
||||||
|
if ($laporan->file_path && Storage::disk('public')->exists($laporan->file_path)) {
|
||||||
|
Storage::disk('public')->delete($laporan->file_path);
|
||||||
|
}
|
||||||
|
|
||||||
|
$laporan->delete();
|
||||||
|
|
||||||
|
return back()->with('success', 'PDF laporan publik berhasil dihapus.');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function bersihkanRiwayatSelesai(Request $request)
|
||||||
|
{
|
||||||
|
$user = $request->user();
|
||||||
|
|
||||||
|
if (!$user || $user->role !== 'admin') {
|
||||||
|
abort(403, 'Akses ditolak: hanya Admin.');
|
||||||
|
}
|
||||||
|
|
||||||
|
$deleted = \App\Models\CalonPenerima::query()
|
||||||
|
->where('tracking_status', 'selesai')
|
||||||
|
->delete();
|
||||||
|
|
||||||
|
return back()->with('success', "Berhasil membersihkan {$deleted} data riwayat selesai. Arsip laporan tetap tersimpan.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -28,6 +28,27 @@ public function store(LoginRequest $request): RedirectResponse
|
||||||
|
|
||||||
$request->session()->regenerate();
|
$request->session()->regenerate();
|
||||||
|
|
||||||
|
$user = Auth::user();
|
||||||
|
|
||||||
|
if (!$user->is_active) {
|
||||||
|
Auth::logout();
|
||||||
|
|
||||||
|
$request->session()->invalidate();
|
||||||
|
$request->session()->regenerateToken();
|
||||||
|
|
||||||
|
return back()->withErrors([
|
||||||
|
'email' => 'Akun Anda belum diaktifkan oleh admin kelurahan.',
|
||||||
|
])->onlyInput('email');
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($user->role === 'admin') {
|
||||||
|
return redirect()->route('admin.dashboard');
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($user->role === 'rt') {
|
||||||
|
return redirect()->route('rt.dashboard');
|
||||||
|
}
|
||||||
|
|
||||||
return redirect()->intended(route('dashboard', absolute: false));
|
return redirect()->intended(route('dashboard', absolute: false));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -39,7 +60,6 @@ public function destroy(Request $request): RedirectResponse
|
||||||
Auth::guard('web')->logout();
|
Auth::guard('web')->logout();
|
||||||
|
|
||||||
$request->session()->invalidate();
|
$request->session()->invalidate();
|
||||||
|
|
||||||
$request->session()->regenerateToken();
|
$request->session()->regenerateToken();
|
||||||
|
|
||||||
return redirect('/');
|
return redirect('/');
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,6 @@
|
||||||
use Illuminate\Auth\Events\Registered;
|
use Illuminate\Auth\Events\Registered;
|
||||||
use Illuminate\Http\RedirectResponse;
|
use Illuminate\Http\RedirectResponse;
|
||||||
use Illuminate\Http\Request;
|
use Illuminate\Http\Request;
|
||||||
use Illuminate\Support\Facades\Auth;
|
|
||||||
use Illuminate\Support\Facades\Hash;
|
use Illuminate\Support\Facades\Hash;
|
||||||
use Illuminate\Validation\Rules;
|
use Illuminate\Validation\Rules;
|
||||||
use Illuminate\View\View;
|
use Illuminate\View\View;
|
||||||
|
|
@ -20,7 +19,6 @@ class RegisteredUserController extends Controller
|
||||||
*/
|
*/
|
||||||
public function create(): View
|
public function create(): View
|
||||||
{
|
{
|
||||||
// Ambil semua RT + dusun untuk dropdown di halaman register
|
|
||||||
$rts = RT::with('dusun')
|
$rts = RT::with('dusun')
|
||||||
->orderBy('dusun_id')
|
->orderBy('dusun_id')
|
||||||
->orderBy('nomor_rt')
|
->orderBy('nomor_rt')
|
||||||
|
|
@ -40,8 +38,6 @@ public function store(Request $request): RedirectResponse
|
||||||
'name' => ['required', 'string', 'max:255'],
|
'name' => ['required', 'string', 'max:255'],
|
||||||
'email' => ['required', 'string', 'lowercase', 'email', 'max:255', 'unique:' . User::class],
|
'email' => ['required', 'string', 'lowercase', 'email', 'max:255', 'unique:' . User::class],
|
||||||
'password' => ['required', 'confirmed', Rules\Password::defaults()],
|
'password' => ['required', 'confirmed', Rules\Password::defaults()],
|
||||||
|
|
||||||
// WAJIB pilih RT biar user punya rt_id
|
|
||||||
'rt_id' => ['required', 'exists:rts,id'],
|
'rt_id' => ['required', 'exists:rts,id'],
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
|
@ -49,19 +45,13 @@ public function store(Request $request): RedirectResponse
|
||||||
'name' => $request->name,
|
'name' => $request->name,
|
||||||
'email' => $request->email,
|
'email' => $request->email,
|
||||||
'password' => Hash::make($request->password),
|
'password' => Hash::make($request->password),
|
||||||
|
|
||||||
// Set default akun hasil register sebagai RT
|
|
||||||
'role' => 'rt',
|
'role' => 'rt',
|
||||||
'rt_id' => $request->rt_id,
|
'rt_id' => $request->rt_id,
|
||||||
|
'is_active' => false,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
event(new Registered($user));
|
event(new Registered($user));
|
||||||
|
|
||||||
Auth::login($user);
|
return redirect()->route('login')->with('success', 'Pendaftaran berhasil. Akun Anda sedang menunggu aktivasi dari admin kelurahan.');
|
||||||
|
|
||||||
// Kamu bisa arahkan ke dashboard RT kalau punya route khusus RT
|
|
||||||
// return redirect()->route('rt.dashboard');
|
|
||||||
|
|
||||||
return redirect(route('dashboard', absolute: false));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -0,0 +1,33 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http\Controllers;
|
||||||
|
|
||||||
|
use App\Models\CalonPenerima;
|
||||||
|
use App\Models\LaporanPublik;
|
||||||
|
use Illuminate\Http\Request;
|
||||||
|
|
||||||
|
class LandingPageController extends Controller
|
||||||
|
{
|
||||||
|
public function index(Request $request)
|
||||||
|
{
|
||||||
|
$nik = trim((string) $request->query('nik',''));
|
||||||
|
|
||||||
|
$statusData = null;
|
||||||
|
|
||||||
|
if($nik !== ''){
|
||||||
|
$statusData = CalonPenerima::with(['rt.dusun'])
|
||||||
|
->where('nik',$nik)
|
||||||
|
->first();
|
||||||
|
}
|
||||||
|
|
||||||
|
$laporanPubliks = LaporanPublik::where('is_active',true)
|
||||||
|
->latest()
|
||||||
|
->get();
|
||||||
|
|
||||||
|
return view('landing',compact(
|
||||||
|
'nik',
|
||||||
|
'statusData',
|
||||||
|
'laporanPubliks'
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,51 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http\Controllers\Rt;
|
||||||
|
|
||||||
|
use App\Http\Controllers\Controller;
|
||||||
|
use App\Models\PenerimaFinal;
|
||||||
|
use App\Models\Rt;
|
||||||
|
use Illuminate\Http\Request;
|
||||||
|
|
||||||
|
class LaporanController extends Controller
|
||||||
|
{
|
||||||
|
public function index(Request $request)
|
||||||
|
{
|
||||||
|
$user = $request->user();
|
||||||
|
|
||||||
|
if (!$user || $user->role !== 'rt') {
|
||||||
|
abort(403, 'Akses ditolak: hanya RT.');
|
||||||
|
}
|
||||||
|
|
||||||
|
$rt = Rt::with('dusun')->find($user->rt_id);
|
||||||
|
|
||||||
|
if (!$rt) {
|
||||||
|
$laporans = collect();
|
||||||
|
$stats = [
|
||||||
|
'total' => 0,
|
||||||
|
'diterima' => 0,
|
||||||
|
'ditolak' => 0,
|
||||||
|
];
|
||||||
|
|
||||||
|
return view('rt.laporan', compact('laporans', 'stats'));
|
||||||
|
}
|
||||||
|
|
||||||
|
$nomorRt = $rt->nomor_rt;
|
||||||
|
$namaDusun = $rt->dusun->nama_dusun ?? null;
|
||||||
|
|
||||||
|
$laporans = PenerimaFinal::query()
|
||||||
|
->where('nomor_rt', $nomorRt)
|
||||||
|
->where('nama_dusun', $namaDusun)
|
||||||
|
->whereIn('status_verifikasi', ['disetujui', 'ditolak'])
|
||||||
|
->orderByDesc('tanggal_penetapan')
|
||||||
|
->get();
|
||||||
|
|
||||||
|
$stats = [
|
||||||
|
'total' => $laporans->count(),
|
||||||
|
'diterima' => $laporans->where('status_verifikasi', 'disetujui')->count(),
|
||||||
|
'ditolak' => $laporans->where('status_verifikasi', 'ditolak')->count(),
|
||||||
|
];
|
||||||
|
|
||||||
|
return view('rt.laporan', compact('laporans', 'stats'));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,23 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Models;
|
||||||
|
|
||||||
|
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||||
|
use Illuminate\Database\Eloquent\Model;
|
||||||
|
|
||||||
|
class LaporanPublik extends Model
|
||||||
|
{
|
||||||
|
use HasFactory;
|
||||||
|
|
||||||
|
protected $fillable = [
|
||||||
|
'judul',
|
||||||
|
'file_path',
|
||||||
|
'uploaded_by',
|
||||||
|
'is_active',
|
||||||
|
];
|
||||||
|
|
||||||
|
public function uploader()
|
||||||
|
{
|
||||||
|
return $this->belongsTo(User::class, 'uploaded_by');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -10,18 +10,31 @@ class PenerimaFinal extends Model
|
||||||
use HasFactory;
|
use HasFactory;
|
||||||
|
|
||||||
protected $fillable = [
|
protected $fillable = [
|
||||||
'calon_penerima_id', 'tanggal_penetapan', 'periode_bantuan',
|
'nama_lengkap',
|
||||||
'jumlah_bantuan', 'status_pencairan', 'tanggal_pencairan',
|
'nik',
|
||||||
|
'no_kk',
|
||||||
|
'rt_id',
|
||||||
|
'nomor_rt',
|
||||||
|
'nama_dusun',
|
||||||
|
'pekerjaan',
|
||||||
|
'penghasilan',
|
||||||
|
'jumlah_tanggungan',
|
||||||
|
'aset_kepemilikan',
|
||||||
|
'bantuan_lain',
|
||||||
|
'usia',
|
||||||
|
'probability',
|
||||||
|
'status_verifikasi',
|
||||||
|
'tanggal_penetapan',
|
||||||
|
'periode_bantuan',
|
||||||
|
'jumlah_bantuan',
|
||||||
];
|
];
|
||||||
|
|
||||||
protected $casts = [
|
protected $casts = [
|
||||||
'tanggal_penetapan' => 'date',
|
'tanggal_penetapan' => 'date',
|
||||||
'tanggal_pencairan' => 'date',
|
|
||||||
'jumlah_bantuan' => 'decimal:2',
|
'jumlah_bantuan' => 'decimal:2',
|
||||||
|
'probability' => 'decimal:4',
|
||||||
|
'penghasilan' => 'integer',
|
||||||
|
'jumlah_tanggungan' => 'integer',
|
||||||
|
'usia' => 'integer',
|
||||||
];
|
];
|
||||||
|
|
||||||
public function calonPenerima()
|
|
||||||
{
|
|
||||||
return $this->belongsTo(CalonPenerima::class);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
@ -2,24 +2,34 @@
|
||||||
|
|
||||||
namespace App\Models;
|
namespace App\Models;
|
||||||
|
|
||||||
// use Illuminate\Contracts\Auth\MustVerifyEmail;
|
|
||||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||||
use Illuminate\Foundation\Auth\User as Authenticatable;
|
use Illuminate\Foundation\Auth\User as Authenticatable;
|
||||||
use Illuminate\Notifications\Notifiable;
|
use Illuminate\Notifications\Notifiable;
|
||||||
|
|
||||||
class User extends Authenticatable
|
class User extends Authenticatable
|
||||||
{
|
{
|
||||||
/** @use HasFactory<\Database\Factories\UserFactory> */
|
|
||||||
use HasFactory, Notifiable;
|
use HasFactory, Notifiable;
|
||||||
|
|
||||||
protected $fillable = ['name', 'email', 'password', 'role', 'rt_id'];
|
protected $fillable = [
|
||||||
protected $hidden = ['password', 'remember_token'];
|
'name',
|
||||||
|
'email',
|
||||||
|
'password',
|
||||||
|
'role',
|
||||||
|
'rt_id',
|
||||||
|
'is_active',
|
||||||
|
];
|
||||||
|
|
||||||
|
protected $hidden = [
|
||||||
|
'password',
|
||||||
|
'remember_token',
|
||||||
|
];
|
||||||
|
|
||||||
protected function casts(): array
|
protected function casts(): array
|
||||||
{
|
{
|
||||||
return [
|
return [
|
||||||
'email_verified_at' => 'datetime',
|
'email_verified_at' => 'datetime',
|
||||||
'password' => 'hashed',
|
'password' => 'hashed',
|
||||||
|
'is_active' => 'boolean',
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -7,8 +7,10 @@
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"require": {
|
"require": {
|
||||||
"php": "^8.2",
|
"php": "^8.2",
|
||||||
|
"barryvdh/laravel-dompdf": "^3.1",
|
||||||
"laravel/framework": "^12.0",
|
"laravel/framework": "^12.0",
|
||||||
"laravel/tinker": "^2.10.1"
|
"laravel/tinker": "^2.10.1",
|
||||||
|
"maatwebsite/excel": "*"
|
||||||
},
|
},
|
||||||
"require-dev": {
|
"require-dev": {
|
||||||
"barryvdh/laravel-ide-helper": "^3.6",
|
"barryvdh/laravel-ide-helper": "^3.6",
|
||||||
|
|
|
||||||
File diff suppressed because it is too large
Load Diff
|
|
@ -60,12 +60,11 @@
|
||||||
|--------------------------------------------------------------------------
|
|--------------------------------------------------------------------------
|
||||||
|
|
|
|
||||||
| Here you may specify the default timezone for your application, which
|
| Here you may specify the default timezone for your application, which
|
||||||
| will be used by the PHP date and date-time functions. The timezone
|
| will be used by the PHP date and date-time functions.
|
||||||
| is set to "UTC" by default as it is suitable for most use cases.
|
|
||||||
|
|
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
'timezone' => 'UTC',
|
'timezone' => 'Asia/Jakarta',
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|--------------------------------------------------------------------------
|
|--------------------------------------------------------------------------
|
||||||
|
|
|
||||||
|
|
@ -14,22 +14,32 @@ public function up(): void
|
||||||
Schema::create('penerima_finals', function (Blueprint $table) {
|
Schema::create('penerima_finals', function (Blueprint $table) {
|
||||||
$table->id();
|
$table->id();
|
||||||
|
|
||||||
$table->foreignId('calon_penerima_id')
|
// Snapshot data warga
|
||||||
->constrained('calon_penerimas')
|
$table->string('nama_lengkap');
|
||||||
->onDelete('cascade');
|
$table->string('nik', 20);
|
||||||
|
$table->string('no_kk', 20);
|
||||||
|
|
||||||
|
// Snapshot wilayah
|
||||||
|
$table->unsignedBigInteger('rt_id')->nullable();
|
||||||
|
$table->string('nomor_rt')->nullable();
|
||||||
|
$table->string('nama_dusun')->nullable();
|
||||||
|
|
||||||
|
// Snapshot data penilaian
|
||||||
|
$table->string('pekerjaan')->nullable();
|
||||||
|
$table->bigInteger('penghasilan')->default(0);
|
||||||
|
$table->integer('jumlah_tanggungan')->default(0);
|
||||||
|
$table->string('aset_kepemilikan')->nullable();
|
||||||
|
$table->string('bantuan_lain')->nullable();
|
||||||
|
$table->integer('usia')->default(0);
|
||||||
|
|
||||||
|
// Snapshot hasil prediksi/final
|
||||||
|
$table->decimal('probability', 8, 4)->default(0);
|
||||||
|
$table->enum('status_verifikasi', ['pending', 'disetujui', 'ditolak'])->default('pending');
|
||||||
|
|
||||||
|
// Data arsip bantuan
|
||||||
$table->date('tanggal_penetapan');
|
$table->date('tanggal_penetapan');
|
||||||
|
$table->string('periode_bantuan');
|
||||||
$table->string('periode_bantuan'); // contoh: 2026 Triwulan 1
|
$table->decimal('jumlah_bantuan', 15, 2)->default(300000);
|
||||||
|
|
||||||
$table->decimal('jumlah_bantuan', 15, 2);
|
|
||||||
|
|
||||||
$table->enum('status_pencairan', [
|
|
||||||
'belum_cair',
|
|
||||||
'sudah_cair'
|
|
||||||
])->default('belum_cair');
|
|
||||||
|
|
||||||
$table->date('tanggal_pencairan')->nullable();
|
|
||||||
|
|
||||||
$table->timestamps();
|
$table->timestamps();
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -1,27 +1,22 @@
|
||||||
<?php
|
<?php
|
||||||
|
|
||||||
namespace App\Http\Middleware;
|
use Illuminate\Database\Migrations\Migration;
|
||||||
|
use Illuminate\Database\Schema\Blueprint;
|
||||||
|
use Illuminate\Support\Facades\Schema;
|
||||||
|
|
||||||
use Closure;
|
return new class extends Migration
|
||||||
use Illuminate\Http\Request;
|
|
||||||
use Illuminate\Support\Facades\Auth;
|
|
||||||
|
|
||||||
class EnsureUserIsActive
|
|
||||||
{
|
{
|
||||||
public function handle(Request $request, Closure $next)
|
public function up(): void
|
||||||
{
|
{
|
||||||
$user = $request->user();
|
Schema::table('users', function (Blueprint $table) {
|
||||||
|
$table->boolean('is_active')->default(false)->after('role');
|
||||||
// kalau user login tapi nonaktif -> logout + balik ke login
|
});
|
||||||
if ($user && (int)($user->is_active ?? 1) === 0) {
|
|
||||||
Auth::logout();
|
|
||||||
$request->session()->invalidate();
|
|
||||||
$request->session()->regenerateToken();
|
|
||||||
|
|
||||||
return redirect()->route('login')
|
|
||||||
->with('error', 'Akun kamu sudah dinonaktifkan oleh Admin.');
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return $next($request);
|
public function down(): void
|
||||||
}
|
{
|
||||||
|
Schema::table('users', function (Blueprint $table) {
|
||||||
|
$table->dropColumn('is_active');
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
};
|
||||||
|
|
@ -0,0 +1,31 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
use Illuminate\Database\Migrations\Migration;
|
||||||
|
use Illuminate\Database\Schema\Blueprint;
|
||||||
|
use Illuminate\Support\Facades\Schema;
|
||||||
|
|
||||||
|
return new class extends Migration
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Run the migrations.
|
||||||
|
*/
|
||||||
|
public function up(): void
|
||||||
|
{
|
||||||
|
Schema::create('laporan_publiks', function (Blueprint $table) {
|
||||||
|
$table->id();
|
||||||
|
$table->string('judul');
|
||||||
|
$table->string('file_path');
|
||||||
|
$table->foreignId('uploaded_by')->nullable()->constrained('users')->nullOnDelete();
|
||||||
|
$table->boolean('is_active')->default(true);
|
||||||
|
$table->timestamps();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reverse the migrations.
|
||||||
|
*/
|
||||||
|
public function down(): void
|
||||||
|
{
|
||||||
|
Schema::dropIfExists('laporan_publiks');
|
||||||
|
}
|
||||||
|
};
|
||||||
Binary file not shown.
|
Before Width: | Height: | Size: 0 B After Width: | Height: | Size: 549 KiB |
|
|
@ -0,0 +1,12 @@
|
||||||
|
<VirtualHost *:80>
|
||||||
|
ServerAdmin webmaster@localhost
|
||||||
|
DocumentRoot /var/www/html/public
|
||||||
|
|
||||||
|
<Directory /var/www/html/public>
|
||||||
|
AllowOverride All
|
||||||
|
Require all granted
|
||||||
|
</Directory>
|
||||||
|
|
||||||
|
ErrorLog ${APACHE_LOG_DIR}/error.log
|
||||||
|
CustomLog ${APACHE_LOG_DIR}/access.log combined
|
||||||
|
</VirtualHost>
|
||||||
|
|
@ -1,20 +1,82 @@
|
||||||
<x-app-layout>
|
<x-app-layout>
|
||||||
<x-slot name="header">
|
<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>
|
<div>
|
||||||
<h2 class="font-semibold text-xl text-gray-800 leading-tight">Dashboard Kelurahan</h2>
|
<h2 style="font-size:17px;font-weight:900;color:#0f172a;line-height:1.2;letter-spacing:-.02em;">Dashboard Kelurahan</h2>
|
||||||
<p class="text-sm text-gray-500 mt-0.5">Ringkasan data kelayakan penerima BLT-DD seluruh dusun</p>
|
<p style="font-size:11px;color:#94a3b8;margin-top:2px;font-weight:500;">Ringkasan data kelayakan penerima BLT-DD seluruh dusun</p>
|
||||||
</div>
|
</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">
|
<div style="display:flex;align-items:center;gap:8px;">
|
||||||
<svg class="w-3.5 h-3.5 text-blue-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
||||||
|
{{-- ── BELL NOTIFIKASI ── --}}
|
||||||
|
<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);">
|
||||||
|
<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"/>
|
||||||
|
</svg>
|
||||||
|
<span id="notif-badge"
|
||||||
|
style="display:none;position:absolute;top:-5px;right:-5px;min-width:17px;height:17px;border-radius:99px;background:#ef4444;color:#fff;font-size:9px;font-weight:800;align-items:center;justify-content:center;padding:0 3px;border:2px solid #fff;">0</span>
|
||||||
|
</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;">
|
||||||
|
<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>
|
||||||
|
</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);">
|
||||||
|
<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"/>
|
<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>
|
</svg>
|
||||||
<span id="admin-clock"></span>
|
<span id="admin-clock"></span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
</x-slot>
|
</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;}
|
||||||
|
|
||||||
|
/* 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 */
|
||||||
|
.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 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;}
|
||||||
|
|
||||||
|
/* 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;}
|
||||||
|
.ni-time{font-size:10px;color:#94a3b8;margin-top:2px;}
|
||||||
|
</style>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
|
/* ── Live clock (asli tidak diubah) ── */
|
||||||
function updateAdminClock() {
|
function updateAdminClock() {
|
||||||
const now = new Date();
|
const now = new Date();
|
||||||
const dateStr = now.toLocaleDateString('id-ID', { timeZone:'Asia/Jakarta', weekday:'long', day:'2-digit', month:'long', year:'numeric' });
|
const dateStr = now.toLocaleDateString('id-ID', { timeZone:'Asia/Jakarta', weekday:'long', day:'2-digit', month:'long', year:'numeric' });
|
||||||
|
|
@ -24,131 +86,180 @@ function updateAdminClock() {
|
||||||
}
|
}
|
||||||
updateAdminClock();
|
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();
|
||||||
|
},
|
||||||
|
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 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(); }
|
||||||
|
|
||||||
|
/* 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) {}
|
||||||
|
}
|
||||||
|
|
||||||
|
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'; }
|
||||||
|
});
|
||||||
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="space-y-4">
|
<div style="display:flex;flex-direction:column;gap:16px;">
|
||||||
|
|
||||||
{{-- GREETING + NOTIFIKASI --}}
|
{{-- GREETING + NOTIFIKASI (asli tidak diubah, hanya ganti class ke inline style) --}}
|
||||||
<div class="flex flex-col sm:flex-row gap-3">
|
<div style="display:flex;flex-wrap:wrap;gap:10px;">
|
||||||
<div class="flex items-center gap-3 bg-white border border-gray-100 rounded-xl px-4 py-3 shadow-sm flex-1">
|
<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;">
|
||||||
<div class="w-8 h-8 rounded-full bg-blue-600 flex items-center justify-center text-white text-sm font-bold shrink-0">
|
<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)) }}
|
{{ strtoupper(substr(Auth::user()->name ?? 'A', 0, 1)) }}
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<p class="text-sm font-medium text-gray-800">Selamat datang, <span class="text-blue-600">{{ Auth::user()->name ?? 'Admin' }}</span> 👋</p>
|
<p style="font-size:13px;font-weight:700;color:#0f172a;">Selamat datang, <span style="color:#2563eb;">{{ Auth::user()->name ?? 'Admin' }}</span> 👋</p>
|
||||||
<p class="text-xs text-gray-400">Panel admin — kelola dan pantau seluruh data warga.</p>
|
<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>
|
</div>
|
||||||
<div class="flex items-center gap-3 bg-amber-50 border border-amber-100 rounded-xl px-4 py-3 shadow-sm">
|
<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 class="w-8 h-8 rounded-full bg-amber-500 flex items-center justify-center shrink-0">
|
<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 class="w-4 h-4 text-white" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
<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"/>
|
<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>
|
</svg>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<p class="text-xs font-semibold text-amber-800">{{ $totalPending ?? 0 }} data menunggu verifikasi</p>
|
<p style="font-size:12px;font-weight:700;color:#92400e;">{{ $totalPending ?? 0 }} data menunggu verifikasi</p>
|
||||||
<a href="{{ route('admin.data-warga') }}" class="text-xs text-amber-600 hover:underline">Proses sekarang →</a>
|
<a href="{{ route('admin.data-warga') }}" style="font-size:11px;color:#b45309;text-decoration:none;font-weight:600;">Proses sekarang →</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{{-- STAT CARDS --}}
|
{{-- STAT CARDS (asli tidak diubah, ganti ke inline style) --}}
|
||||||
<div class="grid grid-cols-2 lg:grid-cols-4 gap-3">
|
<div style="display:grid;grid-template-columns:repeat(4,1fr);gap:12px;">
|
||||||
|
|
||||||
<div class="relative bg-blue-600 text-white rounded-xl p-4 flex items-center justify-between overflow-hidden shadow-md shadow-blue-200">
|
<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);">
|
||||||
<div class="absolute -right-3 -top-3 w-20 h-20 bg-white/10 rounded-full"></div>
|
<div class="sc-blob1"></div><div class="sc-blob2"></div>
|
||||||
<div class="absolute -right-1 -bottom-4 w-14 h-14 bg-white/10 rounded-full"></div>
|
<div style="position:relative;z-index:1;">
|
||||||
<div class="relative z-10">
|
<p style="font-size:10.5px;font-weight:600;color:rgba(191,219,254,.9);">Total Warga Masuk</p>
|
||||||
<p class="text-xs font-medium text-blue-100">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 class="text-3xl font-bold mt-0.5">{{ $totalWarga ?? 0 }}</p>
|
<p style="font-size:10px;color:rgba(191,219,254,.75);margin-top:3px;">Dari semua dusun</p>
|
||||||
<p class="text-xs text-blue-200 mt-0.5">Dari semua dusun</p>
|
|
||||||
</div>
|
</div>
|
||||||
<div class="relative z-10 w-11 h-11 bg-white/20 rounded-lg flex items-center justify-center shrink-0">
|
<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 class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
<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>
|
||||||
<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>
|
</div>
|
||||||
|
|
||||||
<div class="relative bg-amber-500 text-white rounded-xl p-4 flex items-center justify-between overflow-hidden shadow-md shadow-amber-200">
|
<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="absolute -right-3 -top-3 w-20 h-20 bg-white/10 rounded-full"></div>
|
<div class="sc-blob1"></div><div class="sc-blob2"></div>
|
||||||
<div class="absolute -right-1 -bottom-4 w-14 h-14 bg-white/10 rounded-full"></div>
|
<div style="position:relative;z-index:1;">
|
||||||
<div class="relative z-10">
|
<p style="font-size:10.5px;font-weight:600;color:rgba(254,243,199,.9);">Pending Verifikasi</p>
|
||||||
<p class="text-xs font-medium text-amber-100">Pending Verifikasi</p>
|
<p style="font-size:32px;font-weight:900;margin-top:2px;line-height:1;letter-spacing:-.04em;">{{ $totalPending ?? 0 }}</p>
|
||||||
<p class="text-3xl font-bold mt-0.5">{{ $totalPending ?? 0 }}</p>
|
<p style="font-size:10px;color:rgba(254,243,199,.75);margin-top:3px;">Belum diproses</p>
|
||||||
<p class="text-xs text-amber-200 mt-0.5">Belum diproses</p>
|
|
||||||
</div>
|
</div>
|
||||||
<div class="relative z-10 w-11 h-11 bg-white/20 rounded-lg flex items-center justify-center shrink-0">
|
<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 class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
<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>
|
||||||
<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>
|
</div>
|
||||||
|
|
||||||
<div class="relative bg-emerald-500 text-white rounded-xl p-4 flex items-center justify-between overflow-hidden shadow-md shadow-emerald-200">
|
<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="absolute -right-3 -top-3 w-20 h-20 bg-white/10 rounded-full"></div>
|
<div class="sc-blob1"></div><div class="sc-blob2"></div>
|
||||||
<div class="absolute -right-1 -bottom-4 w-14 h-14 bg-white/10 rounded-full"></div>
|
<div style="position:relative;z-index:1;">
|
||||||
<div class="relative z-10">
|
<p style="font-size:10.5px;font-weight:600;color:rgba(167,243,208,.9);">Diterima / Ditetapkan</p>
|
||||||
<p class="text-xs font-medium text-emerald-100">Diterima / Ditetapkan</p>
|
<p style="font-size:32px;font-weight:900;margin-top:2px;line-height:1;letter-spacing:-.04em;">{{ $totalPenerima ?? 0 }}</p>
|
||||||
<p class="text-3xl font-bold mt-0.5">{{ $totalPenerima ?? 0 }}</p>
|
<p style="font-size:10px;color:rgba(167,243,208,.75);margin-top:3px;">Sudah ditetapkan</p>
|
||||||
<p class="text-xs text-emerald-200 mt-0.5">Sudah ditetapkan</p>
|
|
||||||
</div>
|
</div>
|
||||||
<div class="relative z-10 w-11 h-11 bg-white/20 rounded-lg flex items-center justify-center shrink-0">
|
<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 class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
<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>
|
||||||
<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>
|
</div>
|
||||||
|
|
||||||
<div class="relative bg-rose-500 text-white rounded-xl p-4 flex items-center justify-between overflow-hidden shadow-md shadow-rose-200">
|
<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="absolute -right-3 -top-3 w-20 h-20 bg-white/10 rounded-full"></div>
|
<div class="sc-blob1"></div><div class="sc-blob2"></div>
|
||||||
<div class="absolute -right-1 -bottom-4 w-14 h-14 bg-white/10 rounded-full"></div>
|
<div style="position:relative;z-index:1;">
|
||||||
<div class="relative z-10">
|
<p style="font-size:10.5px;font-weight:600;color:rgba(254,205,211,.9);">Ditolak</p>
|
||||||
<p class="text-xs font-medium text-rose-100">Ditolak</p>
|
<p style="font-size:32px;font-weight:900;margin-top:2px;line-height:1;letter-spacing:-.04em;">{{ $totalDitolak ?? 0 }}</p>
|
||||||
<p class="text-3xl font-bold mt-0.5">{{ $totalDitolak ?? 0 }}</p>
|
<p style="font-size:10px;color:rgba(254,205,211,.75);margin-top:3px;">Tidak memenuhi syarat</p>
|
||||||
<p class="text-xs text-rose-200 mt-0.5">Tidak memenuhi syarat</p>
|
|
||||||
</div>
|
</div>
|
||||||
<div class="relative z-10 w-11 h-11 bg-white/20 rounded-lg flex items-center justify-center shrink-0">
|
<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 class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
<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>
|
||||||
<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>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{{-- GRAFIK + DATA PER DUSUN --}}
|
{{-- GRAFIK + DATA PER DUSUN (logika PHP asli 100% tidak diubah) --}}
|
||||||
<div class="grid grid-cols-1 lg:grid-cols-3 gap-4">
|
<div style="display:grid;grid-template-columns:2fr 1fr;gap:14px;">
|
||||||
|
|
||||||
{{-- ✅ GRAFIK POLIGON LINE PER DUSUN (SVG murni, seperti referensi) --}}
|
<div class="db-card">
|
||||||
<div class="lg:col-span-2 bg-white rounded-2xl shadow-sm border border-gray-100 overflow-hidden">
|
<div style="padding:12px 18px;border-bottom:1px solid #f1f5f9;background:#fafbfc;display:flex;align-items:center;justify-content:space-between;">
|
||||||
<div class="px-5 py-3 border-b border-gray-100 flex items-center justify-between bg-gray-50">
|
<div style="display:flex;align-items:center;gap:7px;">
|
||||||
<div class="flex items-center gap-2">
|
<div style="width:3px;height:16px;background:linear-gradient(180deg,#3b82f6,#2563eb);border-radius:4px;"></div>
|
||||||
<div class="w-1 h-4 bg-blue-600 rounded-full"></div>
|
<h3 style="font-size:13px;font-weight:700;color:#1e293b;">Jumlah Warga per Dusun</h3>
|
||||||
<h3 class="text-sm font-semibold text-gray-700">Jumlah Warga per Dusun</h3>
|
|
||||||
</div>
|
</div>
|
||||||
<span class="text-xs text-gray-400 bg-white border border-gray-100 px-2.5 py-0.5 rounded-full">
|
<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 warga
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="p-5">
|
<div style="padding:18px 20px;">
|
||||||
@php
|
@php
|
||||||
$dusuns = $wargaPerDusun->values();
|
$dusuns = $wargaPerDusun->values();
|
||||||
$n = max($dusuns->count(), 1);
|
$n = max($dusuns->count(), 1);
|
||||||
$maxV = max((int)$dusuns->max('total'), 1);
|
$maxV = max((int)$dusuns->max('total'), 1);
|
||||||
|
|
||||||
// Canvas
|
|
||||||
$W = 560; $H = 210;
|
$W = 560; $H = 210;
|
||||||
$pL = 40; $pR = 20;
|
$pL = 40; $pR = 20;
|
||||||
$pT = 30; $pB = 45;
|
$pT = 30; $pB = 45;
|
||||||
$cW = $W - $pL - $pR;
|
$cW = $W - $pL - $pR;
|
||||||
$cH = $H - $pT - $pB;
|
$cH = $H - $pT - $pB;
|
||||||
|
|
||||||
// Y ticks (0 .. maxV, max 5 steps)
|
|
||||||
$step = max(1, (int)ceil($maxV / 4));
|
$step = max(1, (int)ceil($maxV / 4));
|
||||||
$yTicks = range(0, $maxV + $step, $step);
|
$yTicks = range(0, $maxV + $step, $step);
|
||||||
|
|
||||||
// Compute point coords
|
|
||||||
$pts = [];
|
$pts = [];
|
||||||
foreach ($dusuns as $i => $d) {
|
foreach ($dusuns as $i => $d) {
|
||||||
$x = $pL + ($n === 1 ? $cW / 2 : ($i / ($n - 1)) * $cW);
|
$x = $pL + ($n === 1 ? $cW / 2 : ($i / ($n - 1)) * $cW);
|
||||||
|
|
@ -173,87 +284,53 @@ function updateAdminClock() {
|
||||||
</linearGradient>
|
</linearGradient>
|
||||||
</defs>
|
</defs>
|
||||||
|
|
||||||
{{-- Y grid + label --}}
|
|
||||||
@foreach($yTicks as $t)
|
@foreach($yTicks as $t)
|
||||||
@if($t <= $maxV + $step)
|
@if($t <= $maxV + $step)
|
||||||
@php $gy = $pT + $cH - min($t, $maxV) / $maxV * $cH; @endphp
|
@php $gy = $pT + $cH - min($t, $maxV) / $maxV * $cH; @endphp
|
||||||
<line x1="{{ $pL }}" y1="{{ $gy }}" x2="{{ $W - $pR }}" y2="{{ $gy }}"
|
<line x1="{{ $pL }}" y1="{{ $gy }}" x2="{{ $W - $pR }}" y2="{{ $gy }}" stroke="#f1f5f9" stroke-width="1"/>
|
||||||
stroke="#f1f5f9" stroke-width="1"/>
|
<text x="{{ $pL - 5 }}" y="{{ $gy + 4 }}" text-anchor="end" font-size="10" fill="#94a3b8">{{ $t }}</text>
|
||||||
<text x="{{ $pL - 5 }}" y="{{ $gy + 4 }}" text-anchor="end"
|
|
||||||
font-size="10" fill="#94a3b8">{{ $t }}</text>
|
|
||||||
@endif
|
@endif
|
||||||
@endforeach
|
@endforeach
|
||||||
|
|
||||||
{{-- Label sumbu Y --}}
|
<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>
|
||||||
<text x="10" y="{{ $pT + $cH / 2 }}" text-anchor="middle"
|
<line x1="{{ $pL }}" y1="{{ $botY }}" x2="{{ $W - $pR }}" y2="{{ $botY }}" stroke="#cbd5e1" stroke-width="1.5"/>
|
||||||
font-size="9" fill="#94a3b8"
|
<polygon points="{{ $W - $pR }},{{ $botY - 4 }} {{ $W - $pR + 8 }},{{ $botY }} {{ $W - $pR }},{{ $botY + 4 }}" fill="#94a3b8"/>
|
||||||
transform="rotate(-90,10,{{ $pT + $cH / 2 }})">Jumlah Warga</text>
|
<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"/>
|
||||||
{{-- Sumbu X --}}
|
|
||||||
<line x1="{{ $pL }}" y1="{{ $botY }}" x2="{{ $W - $pR }}" y2="{{ $botY }}"
|
|
||||||
stroke="#cbd5e1" stroke-width="1.5"/>
|
|
||||||
{{-- Panah X --}}
|
|
||||||
<polygon points="{{ $W - $pR }},{{ $botY - 4 }} {{ $W - $pR + 8 }},{{ $botY }} {{ $W - $pR }},{{ $botY + 4 }}"
|
|
||||||
fill="#94a3b8"/>
|
|
||||||
|
|
||||||
{{-- Sumbu Y --}}
|
|
||||||
<line x1="{{ $pL }}" y1="{{ $pT }}" x2="{{ $pL }}" y2="{{ $botY }}"
|
|
||||||
stroke="#cbd5e1" stroke-width="1.5"/>
|
|
||||||
{{-- Panah Y --}}
|
|
||||||
<polygon points="{{ $pL - 4 }},{{ $pT }} {{ $pL }},{{ $pT - 8 }} {{ $pL + 4 }},{{ $pT }}"
|
|
||||||
fill="#94a3b8"/>
|
|
||||||
|
|
||||||
{{-- Area fill --}}
|
|
||||||
<path d="{{ $areaPath }}" fill="url(#fillGrad)"/>
|
<path d="{{ $areaPath }}" fill="url(#fillGrad)"/>
|
||||||
|
<polyline points="{{ $poly }}" fill="none" stroke="#2563eb" stroke-width="2.5" stroke-linejoin="round" stroke-linecap="round"/>
|
||||||
{{-- Garis poligon biru --}}
|
|
||||||
<polyline points="{{ $poly }}"
|
|
||||||
fill="none" stroke="#2563eb" stroke-width="2.5"
|
|
||||||
stroke-linejoin="round" stroke-linecap="round"/>
|
|
||||||
|
|
||||||
{{-- Titik & label per dusun --}}
|
|
||||||
@foreach($pts as $p)
|
@foreach($pts as $p)
|
||||||
{{-- Dot --}}
|
<circle cx="{{ $p['x'] }}" cy="{{ $p['y'] }}" r="5" fill="white" stroke="#2563eb" stroke-width="2.5"/>
|
||||||
<circle cx="{{ $p['x'] }}" cy="{{ $p['y'] }}" r="5"
|
<text x="{{ $p['x'] }}" y="{{ $p['y'] - 10 }}" text-anchor="middle" font-size="11" font-weight="700" fill="#2563eb">{{ $p['val'] }}</text>
|
||||||
fill="white" stroke="#2563eb" stroke-width="2.5"/>
|
<text x="{{ $p['x'] }}" y="{{ $botY + 16 }}" text-anchor="middle" font-size="10" fill="#64748b">{{ Str::limit($p['name'], 9) }}</text>
|
||||||
{{-- Nilai di atas titik --}}
|
|
||||||
<text x="{{ $p['x'] }}" y="{{ $p['y'] - 10 }}" text-anchor="middle"
|
|
||||||
font-size="11" font-weight="700" fill="#2563eb">{{ $p['val'] }}</text>
|
|
||||||
{{-- Nama dusun di bawah sumbu X --}}
|
|
||||||
<text x="{{ $p['x'] }}" y="{{ $botY + 16 }}" text-anchor="middle"
|
|
||||||
font-size="10" fill="#64748b">{{ Str::limit($p['name'], 9) }}</text>
|
|
||||||
@endforeach
|
@endforeach
|
||||||
|
<text x="{{ $W - $pR + 10 }}" y="{{ $botY + 4 }}" text-anchor="start" font-size="9" fill="#94a3b8">Dusun</text>
|
||||||
{{-- Label "Nama Dusun" sumbu X --}}
|
|
||||||
<text x="{{ $W - $pR + 10 }}" y="{{ $botY + 4 }}" text-anchor="start"
|
|
||||||
font-size="9" fill="#94a3b8">Dusun</text>
|
|
||||||
</svg>
|
</svg>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{{-- DATA PER DUSUN (tidak diubah) --}}
|
<div class="db-card">
|
||||||
<div class="bg-white rounded-2xl shadow-sm border border-gray-100 overflow-hidden">
|
<div style="padding:12px 18px;border-bottom:1px solid #f1f5f9;background:#fafbfc;display:flex;align-items:center;gap:7px;">
|
||||||
<div class="px-5 py-3 border-b border-gray-100 flex items-center gap-2 bg-gray-50">
|
<div style="width:3px;height:16px;background:linear-gradient(180deg,#10b981,#059669);border-radius:4px;"></div>
|
||||||
<div class="w-1 h-4 bg-emerald-500 rounded-full"></div>
|
<h3 style="font-size:13px;font-weight:700;color:#1e293b;">Data Per Dusun</h3>
|
||||||
<h3 class="text-sm font-semibold text-gray-700">Data Per Dusun</h3>
|
|
||||||
</div>
|
</div>
|
||||||
<div class="p-4 space-y-2.5">
|
<div style="padding:14px;display:flex;flex-direction:column;gap:8px;">
|
||||||
@php $maxDusun = $wargaPerDusun->max('total') ?: 1; @endphp
|
@php $maxDusun = $wargaPerDusun->max('total') ?: 1; @endphp
|
||||||
@foreach($wargaPerDusun as $data)
|
@foreach($wargaPerDusun as $data)
|
||||||
<div class="p-3 bg-gray-50 hover:bg-blue-50 border border-gray-100 hover:border-blue-100 rounded-xl transition-all duration-150">
|
<div style="padding:10px 12px;background:#f8fafc;border:1.5px solid #f1f5f9;border-radius:13px;transition:border-color .15s;">
|
||||||
<div class="flex items-center justify-between mb-1.5">
|
<div style="display:flex;align-items:center;justify-content:space-between;margin-bottom:6px;">
|
||||||
<div class="flex items-center gap-2">
|
<div style="display:flex;align-items:center;gap:8px;">
|
||||||
<div class="w-7 h-7 bg-blue-100 rounded-lg flex items-center justify-center shrink-0">
|
<div style="width:26px;height:26px;background:#eff6ff;border-radius:8px;display:flex;align-items:center;justify-content:center;flex-shrink:0;">
|
||||||
<svg class="w-3.5 h-3.5 text-blue-600" fill="currentColor" viewBox="0 0 20 20">
|
<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"/>
|
<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>
|
</svg>
|
||||||
</div>
|
</div>
|
||||||
<span class="text-sm font-semibold text-gray-800">{{ $data->dusun }}</span>
|
<span style="font-size:12.5px;font-weight:700;color:#1e293b;">{{ $data->dusun }}</span>
|
||||||
</div>
|
</div>
|
||||||
<span class="text-base font-bold text-blue-600">{{ $data->total }}</span>
|
<span style="font-size:14px;font-weight:900;color:#2563eb;">{{ $data->total }}</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="w-full bg-gray-200 rounded-full h-1.5">
|
<div style="width:100%;height:5px;background:#e2e8f0;border-radius:99px;overflow:hidden;">
|
||||||
<div class="h-1.5 bg-blue-500 rounded-full" style="width: {{ ($data->total / $maxDusun) * 100 }}%"></div>
|
<div style="height:100%;background:linear-gradient(90deg,#3b82f6,#2563eb);border-radius:99px;width:{{ ($data->total / $maxDusun) * 100 }}%;"></div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@endforeach
|
@endforeach
|
||||||
|
|
@ -262,88 +339,141 @@ function updateAdminClock() {
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{{-- TOP 10 (tidak diubah) --}}
|
{{-- TOP 10 — full width, tambah kolom Dusun & RT, logika asli 100% tidak diubah --}}
|
||||||
<div class="bg-white rounded-2xl shadow-sm border border-gray-100 overflow-hidden">
|
<div class="db-card tt">
|
||||||
<div class="px-5 py-3 border-b border-gray-100 flex items-center justify-between bg-gray-50">
|
<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 class="flex items-center gap-2">
|
<div style="display:flex;align-items:center;gap:8px;">
|
||||||
<div class="w-1 h-4 bg-blue-600 rounded-full"></div>
|
<div style="width:3px;height:18px;background:linear-gradient(180deg,#3b82f6,#2563eb);border-radius:4px;"></div>
|
||||||
<h3 class="text-sm font-semibold text-gray-700">Top 10 Probabilitas Tertinggi</h3>
|
<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>
|
||||||
</div>
|
</div>
|
||||||
<a href="{{ route('admin.filterisasi') }}" class="text-xs text-blue-600 hover:underline font-medium">Lihat Semua →</a>
|
|
||||||
</div>
|
</div>
|
||||||
<div class="overflow-x-auto">
|
<div style="display:flex;align-items:center;gap:12px;">
|
||||||
<table class="min-w-full text-sm">
|
<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>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div style="overflow-x:auto;">
|
||||||
|
<table>
|
||||||
<thead>
|
<thead>
|
||||||
<tr class="bg-gray-50 border-b border-gray-100">
|
<tr>
|
||||||
<th class="px-4 py-3 text-left text-xs font-semibold text-gray-400 uppercase tracking-wide w-12">Rank</th>
|
<th style="width:48px;">Rank</th>
|
||||||
<th class="px-4 py-3 text-left text-xs font-semibold text-gray-400 uppercase tracking-wide">Nama</th>
|
<th>Nama</th>
|
||||||
<th class="px-4 py-3 text-left text-xs font-semibold text-gray-400 uppercase tracking-wide">NIK</th>
|
<th>NIK</th>
|
||||||
<th class="px-4 py-3 text-left text-xs font-semibold text-gray-400 uppercase tracking-wide">Dusun</th>
|
<th>Dusun</th>
|
||||||
<th class="px-4 py-3 text-left text-xs font-semibold text-gray-400 uppercase tracking-wide">Penghasilan</th>
|
<th>RT</th>
|
||||||
<th class="px-4 py-3 text-left text-xs font-semibold text-gray-400 uppercase tracking-wide">Probabilitas</th>
|
<th>Penghasilan</th>
|
||||||
<th class="px-4 py-3 text-left text-xs font-semibold text-gray-400 uppercase tracking-wide">Status</th>
|
<th style="min-width:160px;">Probabilitas</th>
|
||||||
|
<th>Status</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody class="divide-y divide-gray-50">
|
<tbody>
|
||||||
@forelse($topWarga as $index => $warga)
|
@forelse($topWarga as $index => $warga)
|
||||||
<tr class="hover:bg-blue-50/30 transition-colors duration-150">
|
<tr>
|
||||||
<td class="px-4 py-3">
|
{{-- Rank (asli) --}}
|
||||||
|
<td>
|
||||||
@if($index == 0)
|
@if($index == 0)
|
||||||
<div class="w-7 h-7 rounded-full bg-yellow-100 text-yellow-600 flex items-center justify-center text-xs font-bold">1</div>
|
<div class="rk rk-1">1</div>
|
||||||
@elseif($index == 1)
|
@elseif($index == 1)
|
||||||
<div class="w-7 h-7 rounded-full bg-gray-100 text-gray-500 flex items-center justify-center text-xs font-bold">2</div>
|
<div class="rk rk-2">2</div>
|
||||||
@elseif($index == 2)
|
@elseif($index == 2)
|
||||||
<div class="w-7 h-7 rounded-full bg-orange-100 text-orange-500 flex items-center justify-center text-xs font-bold">3</div>
|
<div class="rk rk-3">3</div>
|
||||||
@else
|
@else
|
||||||
<div class="w-7 h-7 rounded-full bg-blue-50 text-blue-400 flex items-center justify-center text-xs">{{ $index + 1 }}</div>
|
<div class="rk rk-n">{{ $index + 1 }}</div>
|
||||||
@endif
|
@endif
|
||||||
</td>
|
</td>
|
||||||
<td class="px-4 py-3">
|
|
||||||
<div class="flex items-center gap-2">
|
{{-- Nama (asli) --}}
|
||||||
<div class="w-7 h-7 rounded-full bg-gradient-to-br from-blue-500 to-blue-600 flex items-center justify-center shrink-0">
|
<td>
|
||||||
<span class="text-xs font-bold text-white">{{ strtoupper(substr($warga->nama, 0, 1)) }}</span>
|
<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>
|
<div>
|
||||||
<div class="text-sm font-medium text-gray-800">{{ $warga->nama }}</div>
|
<div style="font-size:12.5px;font-weight:700;color:#0f172a;">{{ $warga->nama }}</div>
|
||||||
<div class="text-xs text-gray-400">{{ $warga->pekerjaan }}</div>
|
<div style="font-size:10.5px;color:#94a3b8;margin-top:1px;">{{ $warga->pekerjaan }}</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</td>
|
</td>
|
||||||
<td class="px-4 py-3 text-xs font-mono text-gray-500">{{ $warga->nik }}</td>
|
|
||||||
<td class="px-4 py-3 text-xs text-gray-600">{{ $warga->dusun }}</td>
|
{{-- NIK (asli) --}}
|
||||||
<td class="px-4 py-3 text-xs text-gray-600">Rp {{ number_format($warga->penghasilan, 0, ',', '.') }}</td>
|
<td>
|
||||||
<td class="px-4 py-3">
|
<span style="font-family:monospace;font-size:10.5px;color:#6b7280;background:#f3f4f6;padding:2px 7px;border-radius:6px;">{{ $warga->nik }}</span>
|
||||||
<div class="flex items-center gap-2">
|
</td>
|
||||||
<div class="flex-1 bg-gray-100 rounded-full h-1.5" style="min-width:60px">
|
|
||||||
<div class="h-1.5 rounded-full {{ $warga->probabilitas >= 70 ? 'bg-emerald-500' : ($warga->probabilitas >= 40 ? 'bg-amber-400' : 'bg-rose-400') }}"
|
{{-- Dusun (ditambahkan — pakai $warga->dusun yang sudah ada di data asli) --}}
|
||||||
style="width: {{ min($warga->probabilitas, 100) }}%"></div>
|
<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>
|
</div>
|
||||||
<span class="text-xs font-bold {{ $warga->probabilitas >= 70 ? 'text-emerald-600' : ($warga->probabilitas >= 40 ? 'text-amber-600' : 'text-rose-500') }}">
|
<span style="font-size:12px;font-weight:800;color:{{ $sc }};white-space:nowrap;">
|
||||||
{{ number_format($warga->probabilitas, 1) }}%
|
{{ number_format($warga->probabilitas, 1) }}%
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</td>
|
</td>
|
||||||
<td class="px-4 py-3">
|
|
||||||
|
{{-- Status (asli) --}}
|
||||||
|
<td>
|
||||||
@if($warga->status == 'pending')
|
@if($warga->status == 'pending')
|
||||||
<span class="inline-flex items-center gap-1 px-2.5 py-0.5 text-xs font-semibold rounded-full bg-amber-100 text-amber-700"><span class="w-1.5 h-1.5 rounded-full bg-amber-500"></span>Pending</span>
|
<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')
|
@elseif($warga->status == 'diterima')
|
||||||
<span class="inline-flex items-center gap-1 px-2.5 py-0.5 text-xs font-semibold rounded-full bg-emerald-100 text-emerald-700"><span class="w-1.5 h-1.5 rounded-full bg-emerald-500"></span>Diterima</span>
|
<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')
|
@elseif($warga->status == 'ditolak')
|
||||||
<span class="inline-flex items-center gap-1 px-2.5 py-0.5 text-xs font-semibold rounded-full bg-rose-100 text-rose-700"><span class="w-1.5 h-1.5 rounded-full bg-rose-500"></span>Ditolak</span>
|
<span 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
|
@else
|
||||||
<span class="inline-flex items-center gap-1 px-2.5 py-0.5 text-xs font-semibold rounded-full bg-blue-100 text-blue-700"><span class="w-1.5 h-1.5 rounded-full bg-blue-500"></span>{{ ucfirst($warga->status) }}</span>
|
<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
|
@endif
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
@empty
|
@empty
|
||||||
<tr>
|
<tr>
|
||||||
<td colspan="7" class="px-4 py-10 text-center">
|
<td colspan="8">
|
||||||
<div class="w-10 h-10 bg-gray-100 rounded-full flex items-center justify-center mx-auto mb-2">
|
<div style="padding:48px 16px;text-align:center;">
|
||||||
<svg class="w-5 h-5 text-gray-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
<div style="width:44px;height:44px;background:#f3f4f6;border-radius:50%;display:flex;align-items:center;justify-content:center;margin:0 auto 10px;">
|
||||||
<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 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>
|
||||||
</svg>
|
</div>
|
||||||
|
<p style="font-size:13px;font-weight:600;color:#94a3b8;">Belum ada data warga</p>
|
||||||
</div>
|
</div>
|
||||||
<p class="text-xs text-gray-400">Belum ada data warga</p>
|
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
@endforelse
|
@endforelse
|
||||||
|
|
|
||||||
|
|
@ -49,6 +49,8 @@
|
||||||
.btn-detail{background:#f3f4f6;color:#374151;}
|
.btn-detail{background:#f3f4f6;color:#374151;}
|
||||||
.btn-validasi{background:#eff6ff;color:#1d4ed8;}
|
.btn-validasi{background:#eff6ff;color:#1d4ed8;}
|
||||||
.btn-kirim{background:#fffbeb;color:#b45309;}
|
.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;}
|
.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;}
|
||||||
|
|
||||||
|
|
@ -56,9 +58,10 @@
|
||||||
.prob-bg{flex:1;height:5px;background:#f1f5f9;border-radius:99px;overflow:hidden;}
|
.prob-bg{flex:1;height:5px;background:#f1f5f9;border-radius:99px;overflow:hidden;}
|
||||||
.prob-bar{height:100%;border-radius:99px;}
|
.prob-bar{height:100%;border-radius:99px;}
|
||||||
|
|
||||||
|
/* ── MODAL FIX — scroll handled by .m-wrap, not .m-body ── */
|
||||||
.m-backdrop{position:fixed;inset:0;z-index:40;background:rgba(0,0,0,.45);backdrop-filter:blur(3px);}
|
.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:center;justify-content:center;padding:16px;}
|
.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;max-height:90vh;pointer-events:auto;}
|
.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%);}
|
.mhero{position:relative;overflow:hidden;padding:18px 22px;background:linear-gradient(135deg,#1e40af 0%,#2563eb 55%,#3b82f6 100%);}
|
||||||
.mhero-b1{position:absolute;top:-20px;right:-20px;width:90px;height:90px;background:rgba(255,255,255,.08);border-radius:50%;}
|
.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-b2{position:absolute;bottom:-22px;left:35%;width:70px;height:70px;background:rgba(255,255,255,.06);border-radius:50%;}
|
||||||
|
|
@ -70,7 +73,7 @@
|
||||||
.m-close:hover{background:rgba(255,255,255,.28);}
|
.m-close:hover{background:rgba(255,255,255,.28);}
|
||||||
.m-close svg{width:14px;height:14px;stroke:currentColor;}
|
.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-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:auto;flex:1;padding:14px;background:#f8fafc;display:flex;flex-direction:column;gap:10px;}
|
.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;}
|
.msec{background:#fff;border-radius:14px;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{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 .bar{width:3px;height:13px;border-radius:3px;flex-shrink:0;}
|
||||||
|
|
@ -105,21 +108,39 @@
|
||||||
<div style="width:3px;height:14px;background:#2563eb;border-radius:4px;"></div>
|
<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</h3>
|
||||||
</div>
|
</div>
|
||||||
<form method="GET" action="{{ url()->current() }}" style="display:flex;gap:8px;align-items:center;">
|
|
||||||
<div class="sw" style="flex:1;">
|
<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;">
|
||||||
<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>
|
<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...">
|
<input type="text" name="q" value="{{ request('q') }}" placeholder="Cari NIK, Nama, atau Email...">
|
||||||
</div>
|
</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;box-shadow:0 2px 8px rgba(37,99,235,.25);white-space:nowrap;">
|
||||||
<svg width="13" height="13" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z"/></svg>
|
<svg width="13" height="13" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z"/></svg>
|
||||||
Cari
|
Cari
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
@if(request()->filled('q'))
|
@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;" title="Hapus">
|
||||||
<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>
|
<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>
|
</a>
|
||||||
@endif
|
@endif
|
||||||
</form>
|
</form>
|
||||||
|
|
||||||
|
<div style="margin-top:12px;display:flex;justify-content:flex-end;">
|
||||||
|
<form method="POST"
|
||||||
|
action="{{ route('admin.data-warga.bersihkan') }}"
|
||||||
|
onsubmit="return confirm('Yakin ingin menghapus semua data warga aktif? Data pada laporan arsip tidak akan terhapus.');">
|
||||||
|
@csrf
|
||||||
|
@method('DELETE')
|
||||||
|
<button type="submit" class="btn-reset">
|
||||||
|
<svg width="13" height="13" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||||
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16"/>
|
||||||
|
</svg>
|
||||||
|
Bersihkan Data Warga
|
||||||
|
</button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{{-- TABLE --}}
|
{{-- TABLE --}}
|
||||||
|
|
@ -448,7 +469,8 @@ function openDetailModal(id){
|
||||||
set('d_nik',d.nik||'-');set('d_no_kk',d.no_kk||'-');set('d_nama',d.nama_lengkap||'-');
|
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_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_usia',d.usia?d.usia+' tahun':'-');set('d_kawin',d.status_perkawinan||'-');
|
||||||
set('d_alamat',d.alamat||'-');set('d_dusun',d.desa||'-');
|
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_rt','RT '+String(d.rt||'-').padStart(3,'0'));set('d_aset',d.aset_kepemilikan||'-');
|
||||||
set('d_pekerjaan',d.pekerjaan||'-');
|
set('d_pekerjaan',d.pekerjaan||'-');
|
||||||
set('d_penghasilan','Rp '+Number(d.penghasilan||0).toLocaleString('id-ID'));
|
set('d_penghasilan','Rp '+Number(d.penghasilan||0).toLocaleString('id-ID'));
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,192 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="id">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<title>Laporan Penerima BLT-DD — Desa Ngerong</title>
|
||||||
|
<style>
|
||||||
|
* { box-sizing: border-box; margin: 0; padding: 0; }
|
||||||
|
|
||||||
|
body {
|
||||||
|
font-family: DejaVu Sans, sans-serif;
|
||||||
|
font-size: 10px;
|
||||||
|
color: #1e293b;
|
||||||
|
padding: 26px 30px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ── Header ── */
|
||||||
|
.hd { display: table; width: 100%; border-bottom: 2px solid #2563eb; padding-bottom: 12px; margin-bottom: 14px; }
|
||||||
|
.hd-logo-cell { display: table-cell; vertical-align: middle; width: 48px; }
|
||||||
|
.hd-logo { width: 40px; height: 40px; background: #1e3a8a; border-radius: 9px; text-align: center; line-height: 40px; font-size: 18px; font-weight: 900; color: #fff; }
|
||||||
|
.hd-text-cell { display: table-cell; vertical-align: middle; padding-left: 11px; }
|
||||||
|
.hd-title { font-size: 13px; font-weight: 900; color: #0f172a; text-transform: uppercase; letter-spacing: .02em; }
|
||||||
|
.hd-sub { font-size: 8.5px; color: #64748b; margin-top: 2px; }
|
||||||
|
.hd-meta-cell { display: table-cell; vertical-align: middle; text-align: right; width: 150px; }
|
||||||
|
.hd-badge { display: inline-block; background: #eff6ff; border: 1px solid #bfdbfe; color: #1d4ed8; font-size: 7.5px; font-weight: 700; padding: 2px 7px; border-radius: 4px; text-transform: uppercase; letter-spacing: .05em; }
|
||||||
|
.hd-date { font-size: 7.5px; color: #94a3b8; margin-top: 3px; }
|
||||||
|
|
||||||
|
/* ── Summary ── */
|
||||||
|
.sum-wrap { display: table; width: 100%; margin-bottom: 14px; border: 1px solid #e2e8f0; border-radius: 7px; overflow: hidden; }
|
||||||
|
.sum-cell { display: table-cell; width: 25%; padding: 9px 12px; background: #f8fafc; border-right: 1px solid #e2e8f0; vertical-align: middle; }
|
||||||
|
.sum-cell:last-child { border-right: none; }
|
||||||
|
.sum-val { font-size: 15px; font-weight: 900; color: #0f172a; line-height: 1; }
|
||||||
|
.sum-val.blue { color: #2563eb; }
|
||||||
|
.sum-val.green { color: #16a34a; }
|
||||||
|
.sum-val.amber { color: #d97706; }
|
||||||
|
.sum-val.red { color: #e11d48; }
|
||||||
|
.sum-val.sm { font-size: 11px; }
|
||||||
|
.sum-lbl { font-size: 7px; color: #94a3b8; font-weight: 700; text-transform: uppercase; letter-spacing: .06em; margin-top: 3px; }
|
||||||
|
|
||||||
|
/* ── Section label ── */
|
||||||
|
.sec-lbl { font-size: 8px; font-weight: 700; color: #2563eb; text-transform: uppercase; letter-spacing: .07em; margin-bottom: 6px; padding-left: 6px; border-left: 2.5px solid #2563eb; }
|
||||||
|
|
||||||
|
/* ── Table ── */
|
||||||
|
table.main { width: 100%; border-collapse: collapse; }
|
||||||
|
table.main thead tr { background: #1e40af; }
|
||||||
|
table.main thead th { padding: 7px 6px; text-align: center; font-size: 7.5px; font-weight: 700; color: #fff; text-transform: uppercase; letter-spacing: .04em; border: none; }
|
||||||
|
table.main thead th:first-child { border-radius: 4px 0 0 0; }
|
||||||
|
table.main thead th:last-child { border-radius: 0 4px 0 0; }
|
||||||
|
table.main tbody tr:nth-child(even) { background: #f8fafc; }
|
||||||
|
table.main tbody tr:nth-child(odd) { background: #fff; }
|
||||||
|
table.main tbody td { padding: 5.5px 6px; font-size: 9px; color: #374151; border: none; border-bottom: 1px solid #f1f5f9; vertical-align: middle; }
|
||||||
|
table.main tbody td.mid { text-align: center; }
|
||||||
|
table.main tbody td.right { text-align: right; }
|
||||||
|
|
||||||
|
/* ── Cell elements ── */
|
||||||
|
.av { display: inline-block; width: 18px; height: 18px; background: #2563eb; border-radius: 4px; text-align: center; line-height: 18px; font-size: 8px; font-weight: 700; color: #fff; vertical-align: middle; margin-right: 4px; }
|
||||||
|
.nik { font-family: 'Courier New', monospace; font-size: 7.5px; background: #f1f5f9; padding: 1px 4px; border-radius: 3px; color: #475569; }
|
||||||
|
.rt-b { display: inline-block; background: #eff6ff; border: 1px solid #bfdbfe; color: #1d4ed8; font-size: 7.5px; font-weight: 700; padding: 1px 5px; border-radius: 3px; }
|
||||||
|
.rp { font-weight: 700; color: #166534; background: #f0fdf4; border: 1px solid #bbf7d0; padding: 2px 5px; border-radius: 3px; display: inline-block; white-space: nowrap; font-size: 8.5px; }
|
||||||
|
|
||||||
|
/* Prob bar */
|
||||||
|
.prob-bg { display: inline-block; width: 28px; height: 3px; background: #e2e8f0; border-radius: 99px; vertical-align: middle; margin-right: 3px; overflow: hidden; }
|
||||||
|
|
||||||
|
/* ── Footer ── */
|
||||||
|
.foot { display: table; width: 100%; margin-top: 18px; }
|
||||||
|
.foot-l { display: table-cell; vertical-align: bottom; font-size: 7px; color: #94a3b8; }
|
||||||
|
.foot-l strong { color: #475569; }
|
||||||
|
.foot-r { display: table-cell; vertical-align: bottom; text-align: right; width: 160px; }
|
||||||
|
.ttd-box { border: 1px solid #e2e8f0; border-radius: 5px; padding: 7px 10px; text-align: center; background: #f8fafc; }
|
||||||
|
.ttd-line { width: 90px; border-bottom: 1px solid #334155; margin: 22px auto 3px; }
|
||||||
|
.ttd-name { font-size: 8px; font-weight: 700; color: #1e293b; }
|
||||||
|
.ttd-pos { font-size: 7px; color: #64748b; margin-top: 1px; }
|
||||||
|
.ttd-lbl { font-size: 7px; color: #94a3b8; }
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
|
||||||
|
@php
|
||||||
|
$totalPenerima = $laporans->count();
|
||||||
|
$totalDana = $laporans->sum('jumlah_bantuan') ?: ($totalPenerima * 300000);
|
||||||
|
$avgProb = $laporans->avg(function ($item) {
|
||||||
|
$p = $item->probability ?? 0;
|
||||||
|
return $p <= 1 ? $p * 100 : $p;
|
||||||
|
});
|
||||||
|
$periodeAktif = $laporans->pluck('periode_bantuan')->filter()->unique()->count();
|
||||||
|
@endphp
|
||||||
|
|
||||||
|
{{-- HEADER --}}
|
||||||
|
<div class="hd">
|
||||||
|
<div class="hd-logo-cell"><div class="hd-logo">S</div></div>
|
||||||
|
<div class="hd-text-cell">
|
||||||
|
<div class="hd-title">Laporan Penerima BLT-DD</div>
|
||||||
|
<div class="hd-sub">Desa Ngerong, Kec. Gempol, Kab. Pasuruan — Penerima final BLT Dana Desa</div>
|
||||||
|
</div>
|
||||||
|
<div class="hd-meta-cell">
|
||||||
|
<div class="hd-badge">Laporan Resmi</div>
|
||||||
|
<div class="hd-date">Dicetak: {{ now()->translatedFormat('d F Y') }}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{{-- SUMMARY --}}
|
||||||
|
<div class="sum-wrap">
|
||||||
|
<div class="sum-cell">
|
||||||
|
<div class="sum-val blue">{{ $totalPenerima }}</div>
|
||||||
|
<div class="sum-lbl">Total Penerima</div>
|
||||||
|
</div>
|
||||||
|
<div class="sum-cell">
|
||||||
|
<div class="sum-val green sm">Rp {{ number_format($totalDana, 0, ',', '.') }}</div>
|
||||||
|
<div class="sum-lbl">Total Dana</div>
|
||||||
|
</div>
|
||||||
|
<div class="sum-cell">
|
||||||
|
<div class="sum-val amber">{{ number_format($avgProb, 1) }}%</div>
|
||||||
|
<div class="sum-lbl">Rata-rata Probabilitas</div>
|
||||||
|
</div>
|
||||||
|
<div class="sum-cell">
|
||||||
|
<div class="sum-val red">{{ $periodeAktif }}</div>
|
||||||
|
<div class="sum-lbl">Periode Aktif</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{{-- LABEL --}}
|
||||||
|
<div class="sec-lbl">Daftar Penerima Final</div>
|
||||||
|
|
||||||
|
{{-- TABLE --}}
|
||||||
|
<table class="main">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th style="width:20px;">#</th>
|
||||||
|
<th style="text-align:left;">Nama</th>
|
||||||
|
<th>NIK</th>
|
||||||
|
<th>No KK</th>
|
||||||
|
<th>RT</th>
|
||||||
|
<th>Dusun</th>
|
||||||
|
<th>Probabilitas</th>
|
||||||
|
<th>Tgl Penetapan</th>
|
||||||
|
<th>Periode</th>
|
||||||
|
<th>Jumlah Bantuan</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
@forelse($laporans as $index => $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');
|
||||||
|
$jml = $item->jumlah_bantuan ?? 300000;
|
||||||
|
@endphp
|
||||||
|
<tr>
|
||||||
|
<td class="mid" style="font-size:8px;color:#94a3b8;font-weight:700;">{{ $index + 1 }}</td>
|
||||||
|
<td><span class="av">{{ $init }}</span>{{ $nama }}</td>
|
||||||
|
<td class="mid"><span class="nik">{{ $item->nik ?? '-' }}</span></td>
|
||||||
|
<td class="mid"><span class="nik">{{ $item->no_kk ?? '-' }}</span></td>
|
||||||
|
<td class="mid"><span class="rt-b">RT {{ str_pad($item->nomor_rt ?? '0', 3, '0', STR_PAD_LEFT) }}</span></td>
|
||||||
|
<td style="white-space:nowrap;">{{ $item->nama_dusun ?? '-' }}</td>
|
||||||
|
<td class="mid">
|
||||||
|
<span class="prob-bg">
|
||||||
|
<span style="display:inline-block;width:{{ min($prob,100) }}%;height:3px;background:{{ $sc }};border-radius:99px;vertical-align:top;"></span>
|
||||||
|
</span>
|
||||||
|
<span style="font-size:8.5px;font-weight:700;color:{{ $sc }};">{{ number_format($prob, 1) }}%</span>
|
||||||
|
</td>
|
||||||
|
<td class="mid" style="white-space:nowrap;color:#64748b;">{{ $item->tanggal_penetapan?->format('d-m-Y') ?? '-' }}</td>
|
||||||
|
<td class="mid" style="white-space:nowrap;">{{ $item->periode_bantuan ?? '-' }}</td>
|
||||||
|
<td class="right"><span class="rp">Rp {{ number_format($jml, 0, ',', '.') }}</span></td>
|
||||||
|
</tr>
|
||||||
|
@empty
|
||||||
|
<tr>
|
||||||
|
<td colspan="10" class="mid" style="padding:28px;color:#94a3b8;font-weight:600;">Belum ada data penerima final.</td>
|
||||||
|
</tr>
|
||||||
|
@endforelse
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
{{-- FOOTER --}}
|
||||||
|
<div class="foot">
|
||||||
|
<div class="foot-l">
|
||||||
|
<div style="margin-bottom:2px;"><strong>SiBantuDes</strong> — Sistem Informasi Bantuan Langsung Tunai Dana Desa</div>
|
||||||
|
<div>Digenerate otomatis pada {{ now()->translatedFormat('d F Y, H:i') }} WIB</div>
|
||||||
|
<div style="margin-top:5px;color:#cbd5e1;">{{ $totalPenerima }} penerima • Total Rp {{ number_format($totalDana, 0, ',', '.') }}</div>
|
||||||
|
</div>
|
||||||
|
<div class="foot-r">
|
||||||
|
<div class="ttd-box">
|
||||||
|
<div class="ttd-lbl">Mengetahui,</div>
|
||||||
|
<div class="ttd-line"></div>
|
||||||
|
<div class="ttd-name">Kepala Desa Ngerong</div>
|
||||||
|
<div class="ttd-pos">Kec. Gempol, Kab. Pasuruan</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
|
@ -1,24 +0,0 @@
|
||||||
<x-app-layout>
|
|
||||||
|
|
||||||
@section('content')
|
|
||||||
<div class="space-y-6">
|
|
||||||
|
|
||||||
<div>
|
|
||||||
<h1 class="text-2xl font-bold text-gray-800">Laporan</h1>
|
|
||||||
<p class="text-sm text-gray-500">Rekap data penerima & status, siap untuk cetak/ekspor.</p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="bg-white rounded-xl shadow-sm p-6">
|
|
||||||
<p class="text-gray-700">
|
|
||||||
Placeholder Laporan. Biasanya isi:
|
|
||||||
</p>
|
|
||||||
|
|
||||||
<ul class="list-disc ml-5 mt-3 text-sm text-gray-600 space-y-1">
|
|
||||||
<li>Rekap per dusun</li>
|
|
||||||
<li>Rekap diterima/ditolak</li>
|
|
||||||
<li>Export PDF/Excel</li>
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
</x-app-layout>
|
|
||||||
|
|
@ -0,0 +1,351 @@
|
||||||
|
<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>
|
||||||
|
|
||||||
|
<div class="lp-wrap">
|
||||||
|
|
||||||
|
<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>
|
||||||
|
|
||||||
|
@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>
|
||||||
|
{{ session('success') }}
|
||||||
|
</div>
|
||||||
|
@endif
|
||||||
|
|
||||||
|
@php
|
||||||
|
$totalPenerima = $laporans->count();
|
||||||
|
$totalBantuan = $laporans->sum('jumlah_bantuan');
|
||||||
|
$avgProb = $laporans->avg(function($i){
|
||||||
|
$p = $i->probability ?? 0;
|
||||||
|
return $p <= 1 ? $p * 100 : $p;
|
||||||
|
});
|
||||||
|
$periodes = $laporans->pluck('periode_bantuan')->filter()->unique()->count();
|
||||||
|
@endphp
|
||||||
|
|
||||||
|
<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>
|
||||||
|
</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>
|
||||||
|
</div>
|
||||||
|
<div class="sum-val sm">Rp {{ number_format($totalBantuan, 0, ',', '.') }}</div>
|
||||||
|
<div class="sum-lbl">Total Dana Tersalurkan</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>
|
||||||
|
</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>
|
||||||
|
|
||||||
|
<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>
|
||||||
|
</div>
|
||||||
|
<div class="sum-val">{{ $periodes }}</div>
|
||||||
|
<div class="sum-lbl">Periode Aktif</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="lp-card">
|
||||||
|
<div class="sec-hd">
|
||||||
|
<div class="sec-hd-l">
|
||||||
|
<div class="sec-bar"></div>
|
||||||
|
<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;">
|
||||||
|
@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>
|
||||||
|
</form>
|
||||||
|
|
||||||
|
<span class="exp-meta">{{ $totalPenerima }} penerima terdaftar</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="lp-card">
|
||||||
|
<div class="sec-hd">
|
||||||
|
<div class="sec-hd-l">
|
||||||
|
<div class="sec-bar"></div>
|
||||||
|
<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">
|
||||||
|
@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
|
||||||
|
</button>
|
||||||
|
</form>
|
||||||
|
|
||||||
|
@if(isset($laporanPubliks) && $laporanPubliks->count() > 0)
|
||||||
|
<div class="pub-list">
|
||||||
|
@foreach($laporanPubliks as $pdf)
|
||||||
|
<div class="pub-item">
|
||||||
|
<div>
|
||||||
|
<div class="pub-title">{{ $pdf->judul }}</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;">
|
||||||
|
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')
|
||||||
|
<button type="submit" class="btn-gray">Hapus</button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
@endforeach
|
||||||
|
</div>
|
||||||
|
@endif
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="lp-card">
|
||||||
|
<div class="sec-hd">
|
||||||
|
<div class="sec-hd-l">
|
||||||
|
<div class="sec-bar"></div>
|
||||||
|
<span class="sec-title">Daftar Penerima Final</span>
|
||||||
|
</div>
|
||||||
|
<span class="sec-count">{{ $totalPenerima }} data</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="lp-tbl-wrap">
|
||||||
|
<table class="lp-tbl">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>#</th>
|
||||||
|
<th>Nama</th>
|
||||||
|
<th>NIK</th>
|
||||||
|
<th>No KK</th>
|
||||||
|
<th>RT</th>
|
||||||
|
<th>Dusun</th>
|
||||||
|
<th>Pekerjaan</th>
|
||||||
|
<th>Penghasilan</th>
|
||||||
|
<th>Tanggungan</th>
|
||||||
|
<th>Aset</th>
|
||||||
|
<th>Bantuan Lain</th>
|
||||||
|
<th>Usia</th>
|
||||||
|
<th>Probabilitas</th>
|
||||||
|
<th>Status</th>
|
||||||
|
<th>Tgl Penetapan</th>
|
||||||
|
<th>Periode</th>
|
||||||
|
<th>Jumlah Bantuan</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
@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');
|
||||||
|
|
||||||
|
$bantuanLain = strtolower($item->bantuan_lain ?? 'tidak');
|
||||||
|
@endphp
|
||||||
|
|
||||||
|
<tr>
|
||||||
|
<td>{{ $index + 1 }}</td>
|
||||||
|
|
||||||
|
<td>
|
||||||
|
<div class="name-cell">
|
||||||
|
<div class="av">{{ $init }}</div>
|
||||||
|
<span class="name-txt">{{ $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="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')
|
||||||
|
<span class="bantuan-ya">Ya</span>
|
||||||
|
@else
|
||||||
|
<span class="bantuan-tdk">Tidak</span>
|
||||||
|
@endif
|
||||||
|
</td>
|
||||||
|
|
||||||
|
<td>{{ $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>
|
||||||
|
</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 ?? '-') }}
|
||||||
|
</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>
|
||||||
|
</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>
|
||||||
|
@endforelse
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="tbl-foot">
|
||||||
|
<span class="credit">SiBantuDes · Kelurahan Ngerong</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</x-app-layout>
|
||||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
|
|
@ -7,17 +7,15 @@
|
||||||
|
|
||||||
<title>{{ config('app.name', 'Laravel') }}</title>
|
<title>{{ config('app.name', 'Laravel') }}</title>
|
||||||
|
|
||||||
<!-- Fonts -->
|
<link rel="icon" type="image/png" href="{{ asset('Lambang_Kabupaten_Pasuruan.png') }}">
|
||||||
<link rel="preconnect" href="https://fonts.bunny.net">
|
<link rel="preconnect" href="https://fonts.bunny.net">
|
||||||
<link href="https://fonts.bunny.net/css?family=figtree:400,500,600&display=swap" rel="stylesheet" />
|
<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'])
|
@vite(['resources/css/app.css', 'resources/js/app.js'])
|
||||||
</head>
|
</head>
|
||||||
<body class="font-sans antialiased">
|
<body class="font-sans antialiased">
|
||||||
<div class="min-h-screen bg-gray-100">
|
<div class="min-h-screen bg-gray-100">
|
||||||
|
|
||||||
{{-- BAR TAB + PROFIL (sticky) --}}
|
|
||||||
<div class="bg-white border-b border-gray-200 sticky top-0 z-50 shadow-sm">
|
<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="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-3">
|
||||||
<div class="flex items-center justify-between gap-4">
|
<div class="flex items-center justify-between gap-4">
|
||||||
|
|
@ -26,11 +24,9 @@
|
||||||
$role = Auth::user()->role ?? null;
|
$role = Auth::user()->role ?? null;
|
||||||
@endphp
|
@endphp
|
||||||
|
|
||||||
{{-- TAB MENU (fullwidth) --}}
|
|
||||||
<div class="flex items-center gap-2 flex-1">
|
<div class="flex items-center gap-2 flex-1">
|
||||||
|
|
||||||
@if($role === 'admin')
|
@if($role === 'admin')
|
||||||
{{-- ADMIN MENU --}}
|
|
||||||
<a href="{{ route('admin.dashboard') }}"
|
<a href="{{ route('admin.dashboard') }}"
|
||||||
class="flex-1 text-center px-4 py-2 rounded-xl text-sm font-medium border
|
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' }}">
|
{{ request()->routeIs('admin.dashboard') ? 'bg-blue-600 text-white border-blue-600' : 'bg-white text-gray-700 border-gray-200 hover:bg-gray-50' }}">
|
||||||
|
|
@ -62,7 +58,6 @@ class="flex-1 text-center px-4 py-2 rounded-xl text-sm font-medium border
|
||||||
</a>
|
</a>
|
||||||
|
|
||||||
@else
|
@else
|
||||||
{{-- RT MENU (default) --}}
|
|
||||||
<a href="{{ route('dashboard') }}"
|
<a href="{{ route('dashboard') }}"
|
||||||
class="flex-1 text-center px-4 py-2 rounded-xl text-sm font-medium border
|
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' }}">
|
{{ request()->routeIs('dashboard') ? 'bg-blue-600 text-white border-blue-600' : 'bg-white text-gray-700 border-gray-200 hover:bg-gray-50' }}">
|
||||||
|
|
@ -71,26 +66,25 @@ class="flex-1 text-center px-4 py-2 rounded-xl text-sm font-medium border
|
||||||
|
|
||||||
<a href="{{ route('rt.calon-penerima.create') }}"
|
<a href="{{ route('rt.calon-penerima.create') }}"
|
||||||
class="flex-1 text-center px-4 py-2 rounded-xl text-sm font-medium border
|
class="flex-1 text-center px-4 py-2 rounded-xl text-sm font-medium border
|
||||||
{{ request()->routeIs('calon-penerima.create') ? 'bg-blue-600 text-white border-blue-600' : 'bg-white text-gray-700 border-gray-200 hover:bg-gray-50' }}">
|
{{ 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
|
Pendataan
|
||||||
</a>
|
</a>
|
||||||
|
|
||||||
<a href="{{ route('rt.calon-penerima.index') }}"
|
<a href="{{ route('rt.calon-penerima.index') }}"
|
||||||
class="flex-1 text-center px-4 py-2 rounded-xl text-sm font-medium border
|
class="flex-1 text-center px-4 py-2 rounded-xl text-sm font-medium border
|
||||||
{{ request()->routeIs('calon-penerima.index') ? 'bg-blue-600 text-white border-blue-600' : 'bg-white text-gray-700 border-gray-200 hover:bg-gray-50' }}">
|
{{ 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
|
Riwayat
|
||||||
</a>
|
</a>
|
||||||
|
|
||||||
<a href="{{ route('rt.calon-penerima.index') }}"
|
<a href="{{ route('rt.laporan.index') }}"
|
||||||
class="flex-1 text-center px-4 py-2 rounded-xl text-sm font-medium border
|
class="flex-1 text-center px-4 py-2 rounded-xl text-sm font-medium border
|
||||||
{{ false ? 'bg-blue-600 text-white border-blue-600' : 'bg-white text-gray-700 border-gray-200 hover:bg-gray-50' }}">
|
{{ request()->routeIs('rt.laporan.*') ? 'bg-blue-600 text-white border-blue-600' : 'bg-white text-gray-700 border-gray-200 hover:bg-gray-50' }}">
|
||||||
Status Pengajuan
|
Laporan
|
||||||
</a>
|
</a>
|
||||||
@endif
|
@endif
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{{-- PROFIL (kanan) --}}
|
|
||||||
<div class="flex items-center gap-3">
|
<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">
|
<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">
|
<span class="text-sm font-semibold text-gray-700">
|
||||||
|
|
@ -114,7 +108,6 @@ class="flex-1 text-center px-4 py-2 rounded-xl text-sm font-medium border
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Page Heading -->
|
|
||||||
@isset($header)
|
@isset($header)
|
||||||
<header class="bg-white shadow">
|
<header class="bg-white shadow">
|
||||||
<div class="max-w-7xl mx-auto py-6 px-4 sm:px-6 lg:px-8">
|
<div class="max-w-7xl mx-auto py-6 px-4 sm:px-6 lg:px-8">
|
||||||
|
|
@ -123,7 +116,6 @@ class="flex-1 text-center px-4 py-2 rounded-xl text-sm font-medium border
|
||||||
</header>
|
</header>
|
||||||
@endisset
|
@endisset
|
||||||
|
|
||||||
<!-- Page Content -->
|
|
||||||
<main>
|
<main>
|
||||||
<div class="py-8">
|
<div class="py-8">
|
||||||
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
|
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,12 @@
|
||||||
<nav x-data="{ open: false }" class="bg-white border-b border-gray-100">
|
<nav x-data="{ open: false }" class="bg-white border-b border-gray-100">
|
||||||
<!-- Primary Navigation Menu -->
|
|
||||||
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
|
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
|
||||||
|
|
||||||
<div class="flex justify-between h-16">
|
<div class="flex justify-between h-16">
|
||||||
|
|
||||||
|
<!-- LEFT MENU -->
|
||||||
<div class="flex">
|
<div class="flex">
|
||||||
|
|
||||||
<!-- Logo -->
|
<!-- Logo -->
|
||||||
<div class="shrink-0 flex items-center">
|
<div class="shrink-0 flex items-center">
|
||||||
<a href="{{ route('dashboard') }}">
|
<a href="{{ route('dashboard') }}">
|
||||||
|
|
@ -10,91 +14,109 @@
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{{-- <!-- Navigation Links -->
|
<!-- MENU -->
|
||||||
<div class="hidden space-x-8 sm:-my-px sm:ms-10 sm:flex">
|
<div class="hidden space-x-8 sm:-my-px sm:ms-10 sm:flex">
|
||||||
<x-nav-link :href="route('dashboard')" :active="request()->routeIs('dashboard')">
|
|
||||||
{{ __('Dashboard') }}
|
|
||||||
</x-nav-link>
|
|
||||||
</div>
|
|
||||||
</div> --}}
|
|
||||||
|
|
||||||
<!-- Settings Dropdown -->
|
<x-nav-link :href="route('rt.dashboard')" :active="request()->routeIs('rt.dashboard')">
|
||||||
|
Dashboard
|
||||||
|
</x-nav-link>
|
||||||
|
|
||||||
|
<x-nav-link :href="route('rt.calon-penerima.index')" :active="request()->routeIs('rt.calon-penerima.*')">
|
||||||
|
Pendataan
|
||||||
|
</x-nav-link>
|
||||||
|
|
||||||
|
<x-nav-link :href="route('rt.riwayat.index')" :active="request()->routeIs('rt.riwayat.*')">
|
||||||
|
Riwayat
|
||||||
|
</x-nav-link>
|
||||||
|
|
||||||
|
<x-nav-link :href="route('rt.laporan.index')" :active="request()->routeIs('rt.laporan.*')">
|
||||||
|
Laporan
|
||||||
|
</x-nav-link>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
<!-- USER DROPDOWN -->
|
||||||
<div class="hidden sm:flex sm:items-center sm:ms-6">
|
<div class="hidden sm:flex sm:items-center sm:ms-6">
|
||||||
|
|
||||||
<x-dropdown align="right" width="48">
|
<x-dropdown align="right" width="48">
|
||||||
|
|
||||||
<x-slot name="trigger">
|
<x-slot name="trigger">
|
||||||
<button class="inline-flex items-center px-3 py-2 border border-transparent text-sm leading-4 font-medium rounded-md text-gray-500 bg-white hover:text-gray-700 focus:outline-none transition ease-in-out duration-150">
|
|
||||||
|
<button class="inline-flex items-center px-3 py-2 text-sm font-medium rounded-md text-gray-600 hover:text-gray-800 focus:outline-none">
|
||||||
|
|
||||||
<div>{{ Auth::user()->name }}</div>
|
<div>{{ Auth::user()->name }}</div>
|
||||||
|
|
||||||
<div class="ms-1">
|
<div class="ms-1">
|
||||||
<svg class="fill-current h-4 w-4" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20">
|
<svg class="fill-current h-4 w-4" viewBox="0 0 20 20">
|
||||||
<path fill-rule="evenodd" d="M5.293 7.293a1 1 0 011.414 0L10 10.586l3.293-3.293a1 1 0 111.414 1.414l-4 4a1 1 0 01-1.414 0l-4-4a1 1 0 010-1.414z" clip-rule="evenodd" />
|
<path fill-rule="evenodd"
|
||||||
|
d="M5.293 7.293a1 1 0 011.414 0L10 10.586l3.293-3.293a1 1 0 111.414 1.414l-4
|
||||||
|
4a1 1 0 01-1.414 0l-4-4a1 1 0 010-1.414z"
|
||||||
|
clip-rule="evenodd"/>
|
||||||
</svg>
|
</svg>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
</x-slot>
|
</x-slot>
|
||||||
|
|
||||||
|
|
||||||
<x-slot name="content">
|
<x-slot name="content">
|
||||||
|
|
||||||
<x-dropdown-link :href="route('profile.edit')">
|
<x-dropdown-link :href="route('profile.edit')">
|
||||||
{{ __('Profile') }}
|
Profile
|
||||||
</x-dropdown-link>
|
</x-dropdown-link>
|
||||||
|
|
||||||
<!-- Authentication -->
|
|
||||||
<form method="POST" action="{{ route('logout') }}">
|
<form method="POST" action="{{ route('logout') }}">
|
||||||
@csrf
|
@csrf
|
||||||
|
|
||||||
<x-dropdown-link :href="route('logout')"
|
<x-dropdown-link :href="route('logout')"
|
||||||
onclick="event.preventDefault();
|
onclick="event.preventDefault();
|
||||||
this.closest('form').submit();">
|
this.closest('form').submit();">
|
||||||
{{ __('Log Out') }}
|
Log Out
|
||||||
</x-dropdown-link>
|
</x-dropdown-link>
|
||||||
|
|
||||||
</form>
|
</form>
|
||||||
|
|
||||||
</x-slot>
|
</x-slot>
|
||||||
|
|
||||||
</x-dropdown>
|
</x-dropdown>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Hamburger -->
|
|
||||||
|
|
||||||
|
<!-- HAMBURGER -->
|
||||||
<div class="-me-2 flex items-center sm:hidden">
|
<div class="-me-2 flex items-center sm:hidden">
|
||||||
<button @click="open = ! open" class="inline-flex items-center justify-center p-2 rounded-md text-gray-400 hover:text-gray-500 hover:bg-gray-100 focus:outline-none focus:bg-gray-100 focus:text-gray-500 transition duration-150 ease-in-out">
|
|
||||||
|
<button @click="open = ! open"
|
||||||
|
class="inline-flex items-center justify-center p-2 rounded-md text-gray-400 hover:text-gray-500 hover:bg-gray-100">
|
||||||
|
|
||||||
<svg class="h-6 w-6" stroke="currentColor" fill="none" viewBox="0 0 24 24">
|
<svg class="h-6 w-6" stroke="currentColor" fill="none" viewBox="0 0 24 24">
|
||||||
<path :class="{'hidden': open, 'inline-flex': ! open }" class="inline-flex" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 6h16M4 12h16M4 18h16" />
|
|
||||||
<path :class="{'hidden': ! open, 'inline-flex': open }" class="hidden" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12" />
|
<path :class="{'hidden': open, 'inline-flex': ! open }"
|
||||||
|
stroke-linecap="round"
|
||||||
|
stroke-linejoin="round"
|
||||||
|
stroke-width="2"
|
||||||
|
d="M4 6h16M4 12h16M4 18h16"/>
|
||||||
|
|
||||||
|
<path :class="{'hidden': ! open, 'inline-flex': open }"
|
||||||
|
stroke-linecap="round"
|
||||||
|
stroke-linejoin="round"
|
||||||
|
stroke-width="2"
|
||||||
|
d="M6 18L18 6M6 6l12 12"/>
|
||||||
|
|
||||||
</svg>
|
</svg>
|
||||||
|
|
||||||
</button>
|
</button>
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Responsive Navigation Menu -->
|
|
||||||
<div :class="{'block': open, 'hidden': ! open}" class="hidden sm:hidden">
|
|
||||||
<div class="pt-2 pb-3 space-y-1">
|
|
||||||
<x-responsive-nav-link :href="route('dashboard')" :active="request()->routeIs('dashboard')">
|
|
||||||
{{ __('Dashboard') }}
|
|
||||||
</x-responsive-nav-link>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Responsive Settings Options -->
|
|
||||||
<div class="pt-4 pb-1 border-t border-gray-200">
|
|
||||||
<div class="px-4">
|
|
||||||
<div class="font-medium text-base text-gray-800">{{ Auth::user()->name }}</div>
|
|
||||||
<div class="font-medium text-sm text-gray-500">{{ Auth::user()->email }}</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="mt-3 space-y-1">
|
|
||||||
<x-responsive-nav-link :href="route('profile.edit')">
|
|
||||||
{{ __('Profile') }}
|
|
||||||
</x-responsive-nav-link>
|
|
||||||
|
|
||||||
<!-- Authentication -->
|
|
||||||
<form method="POST" action="{{ route('logout') }}">
|
|
||||||
@csrf
|
|
||||||
|
|
||||||
<x-responsive-nav-link :href="route('logout')"
|
|
||||||
onclick="event.preventDefault();
|
|
||||||
this.closest('form').submit();">
|
|
||||||
{{ __('Log Out') }}
|
|
||||||
</x-responsive-nav-link>
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</nav>
|
</nav>
|
||||||
|
|
@ -1,16 +1,14 @@
|
||||||
<x-app-layout>
|
<x-app-layout>
|
||||||
<x-slot name="header">
|
<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>
|
<div>
|
||||||
<h2 class="font-semibold text-xl text-gray-800 leading-tight">Dashboard</h2>
|
<h2 style="font-size:17px;font-weight:900;color:#0f172a;line-height:1.2;letter-spacing:-.02em;">Dashboard</h2>
|
||||||
<p class="text-sm text-gray-500 mt-0.5">
|
<p style="font-size:11px;color:#94a3b8;margin-top:2px;font-weight:500;">
|
||||||
Ringkasan data pendataran warga {{ Auth::user()->rt->dusun->nama_dusun ?? "" }}
|
Ringkasan data pendataan warga {{ Auth::user()->rt->dusun->nama_dusun ?? '' }}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</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);">
|
||||||
{{-- Tanggal di header --}}
|
<svg width="13" height="13" fill="none" stroke="#3b82f6" viewBox="0 0 24 24">
|
||||||
<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="M8 7V3m8 4V3m-9 8h10M5 21h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v12a2 2 0 002 2z"/>
|
<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>
|
||||||
<span id="live-clock">
|
<span id="live-clock">
|
||||||
|
|
@ -35,190 +33,222 @@ function updateClock() {
|
||||||
</script>
|
</script>
|
||||||
</x-slot>
|
</x-slot>
|
||||||
|
|
||||||
<div class="space-y-4">
|
@php
|
||||||
|
$avgProb = \App\Models\PrediksiKelayakan::whereHas('calonPenerima', function ($q) {
|
||||||
|
$q->where('user_id', Auth::id());
|
||||||
|
})->avg('probability');
|
||||||
|
|
||||||
{{-- Greeting kecil --}}
|
$avgProb = $avgProb ?? 0;
|
||||||
<div class="flex items-center gap-3 bg-white border border-gray-100 rounded-xl px-4 py-3 shadow-sm">
|
|
||||||
<div class="w-8 h-8 rounded-full bg-blue-600 flex items-center justify-center text-white text-sm font-bold shrink-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%;}
|
||||||
|
|
||||||
|
.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: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 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:hover{background:#f0f7ff;}
|
||||||
|
.recent-item:last-child{border-bottom:none;}
|
||||||
|
</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);">
|
||||||
|
<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)) }}
|
{{ strtoupper(substr(Auth::user()->name ?? 'U', 0, 1)) }}
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<p class="text-sm font-medium text-gray-800">
|
<p style="font-size:13.5px;font-weight:700;color:#0f172a;">
|
||||||
Selamat datang, <span class="text-blue-600">{{ Auth::user()->name ?? 'Pengguna' }}</span> 👋
|
Selamat datang, <span style="color:#2563eb;">{{ Auth::user()->name ?? 'Pengguna' }}</span> 👋
|
||||||
</p>
|
</p>
|
||||||
<p class="text-xs text-gray-400">Berikut ringkasan data terkini yang perlu Anda pantau.</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>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{{-- Statistik Cards --}}
|
{{-- STAT CARDS (asli tidak diubah) --}}
|
||||||
<div class="grid grid-cols-2 lg:grid-cols-4 gap-3">
|
<div style="display:grid;grid-template-columns:repeat(4,1fr);gap:12px;">
|
||||||
|
|
||||||
<div class="relative bg-blue-600 text-white rounded-xl p-4 flex items-center justify-between overflow-hidden shadow-md shadow-blue-200">
|
<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);">
|
||||||
<div class="absolute -right-3 -top-3 w-20 h-20 bg-white/10 rounded-full"></div>
|
<div class="sc-blob1"></div><div class="sc-blob2"></div>
|
||||||
<div class="absolute -right-1 -bottom-4 w-14 h-14 bg-white/10 rounded-full"></div>
|
<div style="position:relative;z-index:1;">
|
||||||
<div class="relative z-10">
|
<p style="font-size:10.5px;font-weight:600;color:rgba(191,219,254,.9);">Total Warga</p>
|
||||||
<p class="text-xs font-medium text-blue-100">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 class="text-3xl font-bold mt-0.5">{{ $stats['total_input'] }}</p>
|
|
||||||
</div>
|
</div>
|
||||||
<div class="relative z-10 w-11 h-11 bg-white/20 rounded-lg flex items-center justify-center shrink-0">
|
<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 class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
<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>
|
||||||
<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>
|
</div>
|
||||||
|
|
||||||
<div class="relative bg-amber-500 text-white rounded-xl p-4 flex items-center justify-between overflow-hidden shadow-md shadow-amber-200">
|
<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="absolute -right-3 -top-3 w-20 h-20 bg-white/10 rounded-full"></div>
|
<div class="sc-blob1"></div><div class="sc-blob2"></div>
|
||||||
<div class="absolute -right-1 -bottom-4 w-14 h-14 bg-white/10 rounded-full"></div>
|
<div style="position:relative;z-index:1;">
|
||||||
<div class="relative z-10">
|
<p style="font-size:10.5px;font-weight:600;color:rgba(254,243,199,.9);">Pending</p>
|
||||||
<p class="text-xs font-medium text-amber-100">Pending</p>
|
<p style="font-size:32px;font-weight:900;margin-top:2px;line-height:1;letter-spacing:-.04em;">{{ $stats['pending'] }}</p>
|
||||||
<p class="text-3xl font-bold mt-0.5">{{ $stats['pending'] }}</p>
|
|
||||||
</div>
|
</div>
|
||||||
<div class="relative z-10 w-11 h-11 bg-white/20 rounded-lg flex items-center justify-center shrink-0">
|
<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 class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
<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>
|
||||||
<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>
|
</div>
|
||||||
|
|
||||||
<div class="relative bg-emerald-500 text-white rounded-xl p-4 flex items-center justify-between overflow-hidden shadow-md shadow-emerald-200">
|
<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="absolute -right-3 -top-3 w-20 h-20 bg-white/10 rounded-full"></div>
|
<div class="sc-blob1"></div><div class="sc-blob2"></div>
|
||||||
<div class="absolute -right-1 -bottom-4 w-14 h-14 bg-white/10 rounded-full"></div>
|
<div style="position:relative;z-index:1;">
|
||||||
<div class="relative z-10">
|
<p style="font-size:10.5px;font-weight:600;color:rgba(167,243,208,.9);">Diterima</p>
|
||||||
<p class="text-xs font-medium text-emerald-100">Diterima</p>
|
<p style="font-size:32px;font-weight:900;margin-top:2px;line-height:1;letter-spacing:-.04em;">{{ $stats['disetujui'] }}</p>
|
||||||
<p class="text-3xl font-bold mt-0.5">{{ $stats['disetujui'] }}</p>
|
|
||||||
</div>
|
</div>
|
||||||
<div class="relative z-10 w-11 h-11 bg-white/20 rounded-lg flex items-center justify-center shrink-0">
|
<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 class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
<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>
|
||||||
<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>
|
</div>
|
||||||
|
|
||||||
<div class="relative bg-rose-500 text-white rounded-xl p-4 flex items-center justify-between overflow-hidden shadow-md shadow-rose-200">
|
<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="absolute -right-3 -top-3 w-20 h-20 bg-white/10 rounded-full"></div>
|
<div class="sc-blob1"></div><div class="sc-blob2"></div>
|
||||||
<div class="absolute -right-1 -bottom-4 w-14 h-14 bg-white/10 rounded-full"></div>
|
<div style="position:relative;z-index:1;">
|
||||||
<div class="relative z-10">
|
<p style="font-size:10.5px;font-weight:600;color:rgba(254,205,211,.9);">Ditolak</p>
|
||||||
<p class="text-xs font-medium text-rose-100">Ditolak</p>
|
<p style="font-size:32px;font-weight:900;margin-top:2px;line-height:1;letter-spacing:-.04em;">{{ $stats['ditolak'] }}</p>
|
||||||
<p class="text-3xl font-bold mt-0.5">{{ $stats['ditolak'] }}</p>
|
|
||||||
</div>
|
</div>
|
||||||
<div class="relative z-10 w-11 h-11 bg-white/20 rounded-lg flex items-center justify-center shrink-0">
|
<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 class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
<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>
|
||||||
<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>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{{-- Bottom Section --}}
|
{{-- GRID: Pendataan Terbaru + Aksi Cepat (logika asli 100% tidak diubah) --}}
|
||||||
<div class="grid grid-cols-1 lg:grid-cols-3 gap-4">
|
<div style="display:grid;grid-template-columns:2fr 1fr;gap:14px;">
|
||||||
|
|
||||||
{{-- Pendataran Terbaru --}}
|
{{-- Pendataan Terbaru --}}
|
||||||
<div class="lg:col-span-2 bg-white rounded-xl shadow-sm border border-gray-100 overflow-hidden">
|
<div class="db-card">
|
||||||
<div class="px-4 py-3 border-b border-gray-100 flex items-center justify-between">
|
<div style="padding:12px 18px;border-bottom:1px solid #f1f5f9;background:#fafbfc;display:flex;align-items:center;justify-content:space-between;">
|
||||||
<div class="flex items-center gap-2">
|
<div style="display:flex;align-items:center;gap:7px;">
|
||||||
<div class="w-1 h-4 bg-blue-600 rounded-full"></div>
|
<div style="width:3px;height:16px;background:linear-gradient(180deg,#3b82f6,#2563eb);border-radius:4px;"></div>
|
||||||
<h3 class="text-sm font-semibold text-gray-800">Pendataran Terbaru</h3>
|
<h3 style="font-size:13px;font-weight:700;color:#1e293b;">Pendataan Terbaru</h3>
|
||||||
</div>
|
</div>
|
||||||
<span class="text-xs text-gray-400 bg-gray-50 border border-gray-100 px-2.5 py-0.5 rounded-full">Live</span>
|
<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;">
|
||||||
</div>
|
<span style="width:5px;height:5px;border-radius:50%;background:#10b981;animation:pulse 2s infinite;"></span>
|
||||||
|
Live
|
||||||
<div class="divide-y divide-gray-50">
|
|
||||||
@forelse($recentCalonPenerima as $calon)
|
|
||||||
<div class="flex items-center justify-between px-4 py-3 hover:bg-blue-50/40 transition-colors duration-150">
|
|
||||||
<div class="flex items-center space-x-3">
|
|
||||||
<div class="w-8 h-8 bg-gradient-to-br from-blue-500 to-blue-600 rounded-full flex items-center justify-center shadow-sm shrink-0">
|
|
||||||
<span class="text-xs font-bold text-white">
|
|
||||||
{{ strtoupper(substr($calon->nama_lengkap, 0, 1)) }}
|
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</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>
|
<div>
|
||||||
<p class="text-sm font-medium text-gray-800">{{ $calon->nama_lengkap }}</p>
|
<p style="font-size:12.5px;font-weight:700;color:#0f172a;">{{ $calon->nama_lengkap }}</p>
|
||||||
<p class="text-xs text-gray-400">NIK: {{ $calon->nik }}</p>
|
<p style="font-size:10.5px;color:#94a3b8;margin-top:1px;font-family:monospace;">NIK: {{ $calon->nik }}</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="text-right">
|
<div style="text-align:right;">
|
||||||
<span class="px-2.5 py-0.5 text-xs font-semibold rounded-full
|
@if($calon->status_verifikasi == 'pending')
|
||||||
@if($calon->status_verifikasi == 'pending') bg-amber-100 text-amber-700
|
<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;">
|
||||||
@elseif($calon->status_verifikasi == 'disetujui') bg-emerald-100 text-emerald-700
|
<span style="width:5px;height:5px;border-radius:50%;background:#f59e0b;flex-shrink:0;"></span>
|
||||||
@else bg-rose-100 text-rose-700 @endif">
|
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) }}
|
{{ ucfirst($calon->status_verifikasi) }}
|
||||||
</span>
|
</span>
|
||||||
<p class="text-xs text-gray-400 mt-1">{{ $calon->created_at->diffForHumans() }}</p>
|
@endif
|
||||||
|
<p style="font-size:10px;color:#94a3b8;margin-top:3px;">{{ $calon->created_at->diffForHumans() }}</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@empty
|
@empty
|
||||||
<div class="px-4 py-8 text-center">
|
<div style="padding:44px 16px;text-align:center;">
|
||||||
<div class="w-10 h-10 bg-gray-100 rounded-full flex items-center justify-center mx-auto mb-2">
|
<div style="width:44px;height:44px;background:#f1f5f9;border-radius:50%;display:flex;align-items:center;justify-content:center;margin:0 auto 10px;">
|
||||||
<svg class="w-5 h-5 text-gray-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
<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>
|
||||||
<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 class="text-xs text-gray-400">Belum ada data pendataran</p>
|
<p style="font-size:12.5px;font-weight:600;color:#94a3b8;">Belum ada data pendataan</p>
|
||||||
</div>
|
</div>
|
||||||
@endforelse
|
@endforelse
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{{-- Aksi Cepat --}}
|
{{-- Aksi Cepat (asli tidak diubah) --}}
|
||||||
<div class="bg-white rounded-xl shadow-sm border border-gray-100 p-4">
|
<div class="db-card" style="padding:0;">
|
||||||
<div class="flex items-center gap-2 mb-3">
|
<div style="padding:12px 18px;border-bottom:1px solid #f1f5f9;background:#fafbfc;display:flex;align-items:center;gap:7px;">
|
||||||
<div class="w-1 h-4 bg-blue-600 rounded-full"></div>
|
<div style="width:3px;height:16px;background:linear-gradient(180deg,#10b981,#059669);border-radius:4px;"></div>
|
||||||
<h3 class="text-sm font-semibold text-gray-800">Aksi Cepat</h3>
|
<h3 style="font-size:13px;font-weight:700;color:#1e293b;">Aksi Cepat</h3>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="space-y-2.5">
|
<div style="padding:14px;display:flex;flex-direction:column;gap:10px;">
|
||||||
<a href="{{ route('rt.calon-penerima.create') }}"
|
|
||||||
class="group flex items-center space-x-3 p-3 bg-blue-50 hover:bg-blue-600 border border-blue-100 hover:border-blue-600 rounded-xl transition-all duration-200">
|
{{-- Daftarkan Warga Baru (asli) --}}
|
||||||
<div class="w-9 h-9 bg-blue-600 group-hover:bg-white/20 rounded-lg flex items-center justify-center shrink-0 transition-colors duration-200">
|
<a href="{{ route('rt.calon-penerima.create') }}" class="aksi-btn"
|
||||||
<svg class="w-4 h-4 text-white" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
style="background:#eff6ff;border-color:#bfdbfe;">
|
||||||
<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"/>
|
<div class="aksi-icon" style="background:linear-gradient(135deg,#3b82f6,#2563eb);box-shadow:0 4px 12px rgba(37,99,235,.28);">
|
||||||
</svg>
|
<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>
|
||||||
<div>
|
<div>
|
||||||
<p class="text-sm font-semibold text-gray-800 group-hover:text-white transition-colors">Daftarkan Warga Baru</p>
|
<p style="font-size:12.5px;font-weight:700;color:#1e293b;">Daftarkan Warga Baru</p>
|
||||||
<p class="text-xs text-gray-500 group-hover:text-blue-100 transition-colors">Tambah data calon penerima</p>
|
<p style="font-size:10.5px;color:#64748b;margin-top:1px;">Tambah data calon penerima</p>
|
||||||
</div>
|
</div>
|
||||||
</a>
|
</a>
|
||||||
|
|
||||||
<a href="{{ route('rt.calon-penerima.index') }}"
|
{{-- Lihat Laporan (asli) --}}
|
||||||
class="group flex items-center space-x-3 p-3 bg-emerald-50 hover:bg-emerald-600 border border-emerald-100 hover:border-emerald-600 rounded-xl transition-all duration-200">
|
<a href="{{ route('rt.laporan.index') }}" class="aksi-btn"
|
||||||
<div class="w-9 h-9 bg-emerald-600 group-hover:bg-white/20 rounded-lg flex items-center justify-center shrink-0 transition-colors duration-200">
|
style="background:#f0fdf4;border-color:#bbf7d0;">
|
||||||
<svg class="w-4 h-4 text-white" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
<div class="aksi-icon" style="background:linear-gradient(135deg,#10b981,#059669);box-shadow:0 4px 12px rgba(16,185,129,.28);">
|
||||||
<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 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>
|
||||||
</svg>
|
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<p class="text-sm font-semibold text-gray-800 group-hover:text-white transition-colors">Cek Status</p>
|
<p style="font-size:12.5px;font-weight:700;color:#1e293b;">Lihat Laporan</p>
|
||||||
<p class="text-xs text-gray-500 group-hover:text-emerald-100 transition-colors">Lihat status pengajuan</p>
|
<p style="font-size:10.5px;color:#64748b;margin-top:1px;">Lihat hasil akhir yang dikirim admin</p>
|
||||||
</div>
|
</div>
|
||||||
</a>
|
</a>
|
||||||
|
|
||||||
{{-- Rata-rata Probabilitas --}}
|
{{-- Rata-rata skor (asli, $avgProb tidak diubah) --}}
|
||||||
<div class="p-3 bg-gradient-to-br from-blue-600 to-blue-500 rounded-xl shadow-sm shadow-blue-200">
|
<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 class="flex items-center justify-between mb-0.5">
|
<div style="position:absolute;top:-14px;right:-14px;width:70px;height:70px;background:rgba(255,255,255,.08);border-radius:50%;"></div>
|
||||||
<p class="text-xs font-semibold text-white">Rata-rata Probabilitas</p>
|
<div style="position:absolute;bottom:-18px;left:30%;width:54px;height:54px;background:rgba(255,255,255,.06);border-radius:50%;"></div>
|
||||||
<svg class="w-3.5 h-3.5 text-blue-200" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
<div style="position:relative;z-index:1;">
|
||||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 7h8m0 0v8m0-8l-8 8-4-4-6 6"/>
|
<div style="display:flex;align-items:center;justify-content:space-between;margin-bottom:4px;">
|
||||||
</svg>
|
<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>
|
</div>
|
||||||
|
<p style="font-size:30px;font-weight:900;color:#fff;line-height:1;letter-spacing:-.04em;margin-bottom:4px;">
|
||||||
@php
|
{{ number_format($avgProb, 1) }}<span style="font-size:14px;font-weight:600;color:rgba(191,219,254,.8);">%</span>
|
||||||
$avgProb = \App\Models\PrediksiKelayakan::whereHas('calonPenerima', function($q) {
|
|
||||||
$q->where('user_id', Auth::id());
|
|
||||||
})->avg('probability');
|
|
||||||
@endphp
|
|
||||||
|
|
||||||
<p class="text-2xl font-bold text-white mt-0.5">
|
|
||||||
{{ number_format($avgProb ?? 0, 2) }}<span class="text-sm text-blue-200">%</span>
|
|
||||||
</p>
|
</p>
|
||||||
<p class="text-xs text-blue-200 mt-0.5">Berdasarkan prediksi kelayakan</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>
|
</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
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
@keyframes pulse {
|
||||||
|
0%, 100% { opacity: 1; transform: scale(1); }
|
||||||
|
50% { opacity: .5; transform: scale(.85); }
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
</x-app-layout>
|
</x-app-layout>
|
||||||
|
|
@ -0,0 +1,249 @@
|
||||||
|
<x-app-layout>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.lk-wrap{max-width:1200px;margin:0 auto;padding:24px 20px 40px;}
|
||||||
|
|
||||||
|
/* 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;}
|
||||||
|
|
||||||
|
/* 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;}
|
||||||
|
|
||||||
|
/* 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;}
|
||||||
|
|
||||||
|
/* 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;}
|
||||||
|
|
||||||
|
/* 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;}
|
||||||
|
|
||||||
|
/* 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();
|
||||||
|
@endphp
|
||||||
|
|
||||||
|
<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>
|
||||||
|
</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>
|
||||||
|
</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>
|
||||||
|
</div>
|
||||||
|
<div class="sum-val" style="color:#e11d48;">{{ $ditolak }}</div>
|
||||||
|
<div class="sum-lbl">Ditolak</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{{-- ── TABLE ── --}}
|
||||||
|
<div class="lk-card">
|
||||||
|
<div class="sec-hd">
|
||||||
|
<div class="sec-hd-l">
|
||||||
|
<div class="sec-bar"></div>
|
||||||
|
<span class="sec-title">Daftar Hasil Verifikasi Warga</span>
|
||||||
|
</div>
|
||||||
|
<span class="sec-count">{{ $total }} data</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="lk-tbl-wrap">
|
||||||
|
<table class="lk-tbl">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th style="width:32px;">#</th>
|
||||||
|
<th>Nama</th>
|
||||||
|
<th>NIK</th>
|
||||||
|
<th>RT</th>
|
||||||
|
<th>Dusun</th>
|
||||||
|
<th>Pekerjaan</th>
|
||||||
|
<th>Penghasilan</th>
|
||||||
|
<th>Tanggungan</th>
|
||||||
|
<th>Aset</th>
|
||||||
|
<th>Bantuan Lain</th>
|
||||||
|
<th>Usia</th>
|
||||||
|
<th>Probabilitas</th>
|
||||||
|
<th>Status</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
@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');
|
||||||
|
|
||||||
|
$colors = ['#3b82f6','#8b5cf6','#ec4899','#f59e0b','#10b981','#ef4444','#06b6d4'];
|
||||||
|
$avColor = $colors[ord($init) % count($colors)];
|
||||||
|
|
||||||
|
$diterima = $item->status_verifikasi === 'disetujui';
|
||||||
|
@endphp
|
||||||
|
<tr>
|
||||||
|
<td style="text-align:center;font-size:10.5px;color:#94a3b8;font-weight:700;">{{ $i + 1 }}</td>
|
||||||
|
|
||||||
|
<td>
|
||||||
|
<div class="name-cell">
|
||||||
|
<div class="av" style="background:{{ $avColor }};">{{ $init }}</div>
|
||||||
|
<div>
|
||||||
|
<div style="font-size:12.5px;font-weight:700;color:#0f172a;">{{ $nama }}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</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;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')
|
||||||
|
<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>
|
||||||
|
<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>
|
||||||
|
</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>
|
||||||
|
@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
|
||||||
|
</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>
|
||||||
|
@endforelse
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="tbl-foot">
|
||||||
|
<span class="credit">SiBantuDes · Kelurahan Ngerong</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</x-app-layout>
|
||||||
|
|
@ -7,21 +7,23 @@
|
||||||
use App\Models\CalonPenerima;
|
use App\Models\CalonPenerima;
|
||||||
use App\Models\Dusun;
|
use App\Models\Dusun;
|
||||||
|
|
||||||
|
use App\Http\Controllers\LandingPageController;
|
||||||
|
|
||||||
use App\Http\Controllers\Admin\AdminController;
|
use App\Http\Controllers\Admin\AdminController;
|
||||||
use App\Http\Controllers\Admin\FilterisasiController;
|
use App\Http\Controllers\Admin\FilterisasiController;
|
||||||
|
use App\Http\Controllers\Admin\LaporanController;
|
||||||
|
|
||||||
// RT Controllers
|
// RT Controllers
|
||||||
use App\Http\Controllers\Rt\DashboardController as RtDashboardController;
|
use App\Http\Controllers\Rt\DashboardController as RtDashboardController;
|
||||||
use App\Http\Controllers\Rt\CalonPenerimaController as RtCalonPenerimaController;
|
use App\Http\Controllers\Rt\CalonPenerimaController as RtCalonPenerimaController;
|
||||||
|
use App\Http\Controllers\Rt\LaporanController as RtLaporanController;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|--------------------------------------------------------------------------
|
|--------------------------------------------------------------------------
|
||||||
| ROOT
|
| ROOT / LANDING PAGE PUBLIK
|
||||||
|--------------------------------------------------------------------------
|
|--------------------------------------------------------------------------
|
||||||
*/
|
*/
|
||||||
Route::get('/', function () {
|
Route::get('/', [LandingPageController::class, 'index'])->name('landing');
|
||||||
return redirect()->route('login');
|
|
||||||
});
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|--------------------------------------------------------------------------
|
|--------------------------------------------------------------------------
|
||||||
|
|
@ -59,6 +61,7 @@
|
||||||
// DASHBOARD ADMIN
|
// DASHBOARD ADMIN
|
||||||
Route::get('/dashboard', function (Request $request) {
|
Route::get('/dashboard', function (Request $request) {
|
||||||
$user = $request->user();
|
$user = $request->user();
|
||||||
|
|
||||||
if (!$user || $user->role !== 'admin') {
|
if (!$user || $user->role !== 'admin') {
|
||||||
abort(403, 'Akses ditolak: hanya Admin.');
|
abort(403, 'Akses ditolak: hanya Admin.');
|
||||||
}
|
}
|
||||||
|
|
@ -66,10 +69,13 @@
|
||||||
$visibleStatuses = ['terkirim', 'sedang_validasi', 'selesai'];
|
$visibleStatuses = ['terkirim', 'sedang_validasi', 'selesai'];
|
||||||
|
|
||||||
$totalWarga = CalonPenerima::whereIn('tracking_status', $visibleStatuses)->count();
|
$totalWarga = CalonPenerima::whereIn('tracking_status', $visibleStatuses)->count();
|
||||||
|
|
||||||
$totalDusun = Dusun::count();
|
$totalDusun = Dusun::count();
|
||||||
|
|
||||||
$totalPenerima = CalonPenerima::whereIn('tracking_status', $visibleStatuses)
|
$totalPenerima = CalonPenerima::whereIn('tracking_status', $visibleStatuses)
|
||||||
->where('status_verifikasi', 'disetujui')
|
->where('status_verifikasi', 'disetujui')
|
||||||
->count();
|
->count();
|
||||||
|
|
||||||
$totalPending = CalonPenerima::whereIn('tracking_status', $visibleStatuses)
|
$totalPending = CalonPenerima::whereIn('tracking_status', $visibleStatuses)
|
||||||
->where('status_verifikasi', 'pending')
|
->where('status_verifikasi', 'pending')
|
||||||
->count();
|
->count();
|
||||||
|
|
@ -122,6 +128,7 @@
|
||||||
// DATA WARGA + SEARCH
|
// DATA WARGA + SEARCH
|
||||||
Route::get('/data-warga', function (Request $request) {
|
Route::get('/data-warga', function (Request $request) {
|
||||||
$user = $request->user();
|
$user = $request->user();
|
||||||
|
|
||||||
if (!$user || $user->role !== 'admin') {
|
if (!$user || $user->role !== 'admin') {
|
||||||
abort(403, 'Akses ditolak: hanya Admin.');
|
abort(403, 'Akses ditolak: hanya Admin.');
|
||||||
}
|
}
|
||||||
|
|
@ -150,9 +157,30 @@
|
||||||
return view('admin.data-warga', compact('dusuns', 'wargas'));
|
return view('admin.data-warga', compact('dusuns', 'wargas'));
|
||||||
})->name('data-warga');
|
})->name('data-warga');
|
||||||
|
|
||||||
|
// TAMBAHAN: BERSIHKAN DATA WARGA AKTIF
|
||||||
|
Route::delete('/data-warga/bersihkan', function (Request $request) {
|
||||||
|
$user = $request->user();
|
||||||
|
|
||||||
|
if (!$user || $user->role !== 'admin') {
|
||||||
|
abort(403, 'Akses ditolak: hanya Admin.');
|
||||||
|
}
|
||||||
|
|
||||||
|
DB::transaction(function () {
|
||||||
|
// hapus hasil prediksi yang terkait data aktif
|
||||||
|
DB::table('prediksi_kelayakans')->delete();
|
||||||
|
|
||||||
|
// hapus data pengajuan aktif
|
||||||
|
DB::table('calon_penerimas')->delete();
|
||||||
|
});
|
||||||
|
|
||||||
|
return redirect()->route('admin.data-warga')
|
||||||
|
->with('success', 'Semua data warga aktif berhasil dibersihkan. Arsip laporan tetap aman.');
|
||||||
|
})->name('data-warga.bersihkan');
|
||||||
|
|
||||||
// MULAI VALIDASI
|
// MULAI VALIDASI
|
||||||
Route::post('/data-warga/{id}/mulai-validasi', function (Request $request, $id) {
|
Route::post('/data-warga/{id}/mulai-validasi', function (Request $request, $id) {
|
||||||
$user = $request->user();
|
$user = $request->user();
|
||||||
|
|
||||||
if (!$user || $user->role !== 'admin') {
|
if (!$user || $user->role !== 'admin') {
|
||||||
abort(403, 'Akses ditolak: hanya Admin.');
|
abort(403, 'Akses ditolak: hanya Admin.');
|
||||||
}
|
}
|
||||||
|
|
@ -174,6 +202,7 @@
|
||||||
// KIRIM HASIL VALIDASI KE RT
|
// KIRIM HASIL VALIDASI KE RT
|
||||||
Route::post('/data-warga/{id}/selesai-validasi', function (Request $request, $id) {
|
Route::post('/data-warga/{id}/selesai-validasi', function (Request $request, $id) {
|
||||||
$user = $request->user();
|
$user = $request->user();
|
||||||
|
|
||||||
if (!$user || $user->role !== 'admin') {
|
if (!$user || $user->role !== 'admin') {
|
||||||
abort(403, 'Akses ditolak: hanya Admin.');
|
abort(403, 'Akses ditolak: hanya Admin.');
|
||||||
}
|
}
|
||||||
|
|
@ -195,7 +224,7 @@
|
||||||
return back()->with('success', 'Hasil validasi berhasil dikirim ke RT.');
|
return back()->with('success', 'Hasil validasi berhasil dikirim ke RT.');
|
||||||
})->name('data-warga.selesai-validasi');
|
})->name('data-warga.selesai-validasi');
|
||||||
|
|
||||||
// DATA AKUN (AdminController)
|
// DATA AKUN
|
||||||
Route::get('/data-akun', [AdminController::class, 'dataAkun'])->name('data-akun');
|
Route::get('/data-akun', [AdminController::class, 'dataAkun'])->name('data-akun');
|
||||||
Route::post('/data-akun/{id}/role', [AdminController::class, 'ubahRole'])->name('data-akun.role');
|
Route::post('/data-akun/{id}/role', [AdminController::class, 'ubahRole'])->name('data-akun.role');
|
||||||
Route::post('/data-akun/{id}/toggle-aktif', [AdminController::class, 'toggleAktif'])->name('data-akun.toggle-aktif');
|
Route::post('/data-akun/{id}/toggle-aktif', [AdminController::class, 'toggleAktif'])->name('data-akun.toggle-aktif');
|
||||||
|
|
@ -206,8 +235,15 @@
|
||||||
Route::post('/filterisasi/tetapkan', [FilterisasiController::class, 'tetapkan'])->name('filterisasi.tetapkan');
|
Route::post('/filterisasi/tetapkan', [FilterisasiController::class, 'tetapkan'])->name('filterisasi.tetapkan');
|
||||||
Route::post('/filterisasi/reset', [FilterisasiController::class, 'resetDusun'])->name('filterisasi.reset');
|
Route::post('/filterisasi/reset', [FilterisasiController::class, 'resetDusun'])->name('filterisasi.reset');
|
||||||
|
|
||||||
// LAPORAN
|
// LAPORAN ADMIN
|
||||||
Route::get('/laporan', fn () => view('admin.laporan'))->name('laporan');
|
Route::get('/laporan', [LaporanController::class, 'index'])->name('laporan');
|
||||||
|
Route::get('/laporan/pdf', [LaporanController::class, 'exportPdf'])->name('laporan.pdf');
|
||||||
|
Route::get('/laporan/excel', [LaporanController::class, 'exportExcel'])->name('laporan.excel');
|
||||||
|
Route::post('/laporan/kirim-ke-rt', [LaporanController::class, 'kirimKeRt'])->name('laporan.kirim-ke-rt');
|
||||||
|
Route::post('/laporan/upload-publik-pdf', [LaporanController::class, 'uploadPublikPdf'])->name('laporan.upload-publik-pdf');
|
||||||
|
Route::delete('/laporan/publik-pdf/{id}', [LaporanController::class, 'hapusPublikPdf'])->name('laporan.hapus-publik-pdf');
|
||||||
|
Route::delete('/laporan/bersihkan-riwayat-selesai', [LaporanController::class, 'bersihkanRiwayatSelesai'])
|
||||||
|
->name('laporan.bersihkan-riwayat-selesai');
|
||||||
});
|
});
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
|
@ -215,7 +251,7 @@
|
||||||
| RT ROUTES (RT ONLY)
|
| RT ROUTES (RT ONLY)
|
||||||
|--------------------------------------------------------------------------
|
|--------------------------------------------------------------------------
|
||||||
*/
|
*/
|
||||||
Route::middleware(['auth', 'verified'])
|
Route::middleware(['auth', 'verified', 'is_active'])
|
||||||
->prefix('rt')
|
->prefix('rt')
|
||||||
->name('rt.')
|
->name('rt.')
|
||||||
->group(function () {
|
->group(function () {
|
||||||
|
|
@ -223,6 +259,9 @@
|
||||||
// DASHBOARD RT
|
// DASHBOARD RT
|
||||||
Route::get('/dashboard', [RtDashboardController::class, 'index'])->name('dashboard');
|
Route::get('/dashboard', [RtDashboardController::class, 'index'])->name('dashboard');
|
||||||
|
|
||||||
|
// LAPORAN RT
|
||||||
|
Route::get('/laporan', [RtLaporanController::class, 'index'])->name('laporan.index');
|
||||||
|
|
||||||
// AJUKAN DATA KE ADMIN
|
// AJUKAN DATA KE ADMIN
|
||||||
Route::patch('/calon-penerima/{calonPenerima}/ajukan', [RtCalonPenerimaController::class, 'ajukan'])
|
Route::patch('/calon-penerima/{calonPenerima}/ajukan', [RtCalonPenerimaController::class, 'ajukan'])
|
||||||
->name('calon-penerima.ajukan');
|
->name('calon-penerima.ajukan');
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue