66cfbbd523
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
998 lines
57 KiB
PHP
998 lines
57 KiB
PHP
<!DOCTYPE html>
|
|
<html lang="id" data-theme="light">
|
|
<head>
|
|
<meta charset="utf-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
<meta name="csrf-token" content="{{ csrf_token() }}">
|
|
<title>Data Tabular - WebGIS Pemetaan Kemiskinan</title>
|
|
|
|
<!-- DaisyUI + Tailwind -->
|
|
<link href="https://cdn.jsdelivr.net/npm/daisyui@4/dist/full.min.css" rel="stylesheet">
|
|
<script src="https://cdn.tailwindcss.com"></script>
|
|
<!-- Font Awesome -->
|
|
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.0/css/all.min.css">
|
|
<!-- Plus Jakarta Sans Font -->
|
|
<link rel="preconnect" href="https://fonts.googleapis.com">
|
|
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
|
<link href="https://fonts.googleapis.com/css2?family=Plus+Jakarta+Sans:ital,wght@0,200..800;1,200..800&display=swap" rel="stylesheet">
|
|
|
|
<style>
|
|
* { font-family: 'Plus Jakarta Sans', sans-serif; }
|
|
body { font-weight: 500; background-color: #f8fafc; }
|
|
h1, h2, h3, h4, h5, h6, .btn, .card-title, .stat-value { font-weight: 700 !important; letter-spacing: -0.01em; }
|
|
</style>
|
|
</head>
|
|
<body class="min-h-screen">
|
|
|
|
<!-- ═══ NAVBAR ═══════════════════════════════════════════════════════════ -->
|
|
<div class="navbar bg-base-100 border-b border-base-200 shadow-sm sticky top-0 z-[1000]">
|
|
<div class="flex-1">
|
|
<a href="{{ url('/') }}" class="btn btn-ghost text-lg font-bold gap-2 text-primary">
|
|
<i class="fa-solid fa-house-chimney-crack"></i>
|
|
WebGIS Pemetaan Kemiskinan
|
|
</a>
|
|
</div>
|
|
<div class="flex-none">
|
|
<ul class="menu menu-horizontal px-1 gap-1">
|
|
<li><a href="{{ url('/map') }}" class="font-medium"><i class="fa-solid fa-map"></i> Peta</a></li>
|
|
<li><a href="{{ url('/data') }}" class="font-semibold bg-primary/10 text-primary rounded-lg"><i class="fa-solid fa-table-list"></i> Data Tabular</a></li>
|
|
<li>
|
|
<a href="{{ url('/dashboard') }}" class="font-medium">
|
|
<i class="fa-solid fa-chart-bar"></i> Dashboard
|
|
</a>
|
|
</li>
|
|
<li><a href="{{ url('/permohonan') }}" class="font-medium"><i class="fa-solid fa-right-left"></i> Transfer</a></li>
|
|
@if(auth()->user()->hasRole('administrator'))
|
|
<li>
|
|
<a href="{{ url('/admin/users') }}" class="font-medium">
|
|
<i class="fa-solid fa-users-gear"></i> Admin
|
|
</a>
|
|
</li>
|
|
<li>
|
|
<a href="{{ url('/admin/logs') }}" class="font-medium">
|
|
<i class="fa-solid fa-clock-rotate-left"></i> Log
|
|
</a>
|
|
</li>
|
|
<li>
|
|
<a href="{{ url('/admin/settings') }}" class="font-medium">
|
|
<i class="fa-solid fa-sliders"></i> Pengaturan
|
|
</a>
|
|
</li>
|
|
@endif
|
|
</ul>
|
|
<!-- User dropdown -->
|
|
<div class="dropdown dropdown-end ml-2">
|
|
<div tabindex="0" role="button" class="btn btn-ghost gap-2">
|
|
<div class="avatar placeholder">
|
|
<div class="bg-primary text-primary-content rounded-full w-8">
|
|
<span class="text-xs font-bold">{{ substr(auth()->user()->name, 0, 1) }}</span>
|
|
</div>
|
|
</div>
|
|
<span class="hidden sm:inline text-sm font-semibold">{{ auth()->user()->name }}</span>
|
|
<i class="fa-solid fa-chevron-down text-xs opacity-50"></i>
|
|
</div>
|
|
<ul tabindex="0" class="dropdown-content menu bg-base-100 rounded-box z-50 w-52 p-2 shadow-lg border border-base-200 mt-2">
|
|
<li class="menu-title">
|
|
<span class="text-xs text-base-content/50 capitalize">{{ str_replace('_', ' ', auth()->user()->getRoleNames()->first() ?? 'user') }}</span>
|
|
</li>
|
|
<li>
|
|
<form method="POST" action="{{ route('logout') }}">
|
|
@csrf
|
|
<button type="submit" class="text-error w-full text-left flex items-center gap-2">
|
|
<i class="fa-solid fa-right-from-bracket"></i>
|
|
Keluar
|
|
</button>
|
|
</form>
|
|
</li>
|
|
</ul>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- ═══ MAIN CONTENT ════════════════════════════════════════════════════ -->
|
|
<main class="max-w-screen-xl mx-auto px-4 py-8">
|
|
|
|
<!-- Header -->
|
|
<div class="flex flex-wrap items-center justify-between gap-4 mb-8">
|
|
<div>
|
|
<h1 class="text-3xl font-bold flex items-center gap-3">
|
|
<i class="fa-solid fa-database text-primary"></i>
|
|
Manajemen Data
|
|
</h1>
|
|
<p class="text-base-content/50 mt-1">Daftar rumah ibadah dan keluarga miskin dalam matriks tabular.</p>
|
|
</div>
|
|
<div class="flex flex-wrap gap-2">
|
|
<div class="join">
|
|
<input class="input input-bordered join-item input-md w-64" placeholder="Cari data..." id="searchInput" onkeyup="filterData()"/>
|
|
<button class="btn btn-md join-item btn-ghost border-base-300"><i class="fa-solid fa-search"></i></button>
|
|
</div>
|
|
@if(in_array(auth()->user()->getRoleNames()->first(), ['administrator', 'petugas_pendataan', 'pengurus_ibadah', 'pemangku_kebijakan']))
|
|
<button onclick="exportCSV()" class="btn btn-md btn-outline border-base-300 gap-2">
|
|
<i class="fa-solid fa-file-csv"></i> Ekspor CSV
|
|
</button>
|
|
<a href="{{ route('export.pdf.all') }}" target="_blank" class="btn btn-md btn-outline border-base-300 gap-2">
|
|
<i class="fa-solid fa-file-pdf text-error"></i> Ekspor PDF
|
|
</a>
|
|
@endif
|
|
@if(auth()->user()->hasRole('administrator'))
|
|
<button onclick="document.getElementById('modal_import').showModal()" class="btn btn-md btn-outline border-base-300 gap-2">
|
|
<i class="fa-solid fa-file-import text-info"></i> Import CSV
|
|
</button>
|
|
@endif
|
|
<a href="{{ url('/map') }}" class="btn btn-md btn-primary shadow-lg gap-2">
|
|
<i class="fa-solid fa-plus"></i> Tambah Data
|
|
</a>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Stats Grid -->
|
|
<div class="grid grid-cols-1 md:grid-cols-3 gap-6 mb-8">
|
|
<div class="stat bg-white shadow rounded-2xl border border-base-200">
|
|
<div class="stat-figure text-success text-3xl"><i class="fa-solid fa-mosque"></i></div>
|
|
<div class="stat-title text-xs uppercase font-bold opacity-50">Total Rumah Ibadah</div>
|
|
<div class="stat-value text-success" id="countIbadah">-</div>
|
|
</div>
|
|
<div class="stat bg-white shadow rounded-2xl border border-base-200">
|
|
<div class="stat-figure text-error text-3xl"><i class="fa-solid fa-house-user"></i></div>
|
|
<div class="stat-title text-xs uppercase font-bold opacity-50">Total Rumah Miskin</div>
|
|
<div class="stat-value text-error" id="countMiskin">-</div>
|
|
</div>
|
|
<div class="stat bg-white shadow rounded-2xl border border-base-200">
|
|
<div class="stat-figure text-warning text-3xl"><i class="fa-solid fa-users"></i></div>
|
|
<div class="stat-title text-xs uppercase font-bold opacity-50">Total Jiwa Terdata</div>
|
|
<div class="stat-value text-warning" id="countJiwa">-</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Tabs -->
|
|
<div role="tablist" class="tabs tabs-lifted tabs-lg mb-6">
|
|
<input type="radio" name="data_tabs" role="tab" class="tab font-bold" aria-label="🕌 Rumah Ibadah" checked />
|
|
<div role="tabpanel" class="tab-content bg-base-100 border-base-300 rounded-box p-6 shadow-sm">
|
|
@if(auth()->user()->hasRole('administrator'))
|
|
<div id="bulkBar_ibadah" class="hidden alert alert-warning py-2 mb-2 flex items-center justify-between">
|
|
<span class="text-sm font-semibold"><span id="bulkCount_ibadah">0</span> item terpilih</span>
|
|
<button onclick="bulkDelete('ibadah')" class="btn btn-xs btn-error text-white"><i class="fa-solid fa-trash mr-1"></i>Hapus Terpilih</button>
|
|
</div>
|
|
@endif
|
|
<div class="overflow-x-auto">
|
|
<table class="table table-zebra w-full" id="tableIbadah">
|
|
<thead>
|
|
<tr class="text-xs font-bold uppercase opacity-50">
|
|
@if(auth()->user()->hasRole('administrator'))
|
|
<th><input type="checkbox" class="checkbox checkbox-xs" onchange="toggleSelectAll('ibadah', this.checked)"></th>
|
|
@endif
|
|
<th>#</th>
|
|
<th>Nama</th>
|
|
<th>Jenis</th>
|
|
<th>Pengurus/Telp</th>
|
|
<th>Radius</th>
|
|
<th>Koordinat</th>
|
|
<th class="text-center">Aksi</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody id="bodyIbadah"></tbody>
|
|
</table>
|
|
</div>
|
|
</div>
|
|
|
|
<input type="radio" name="data_tabs" role="tab" class="tab font-bold" aria-label="🏠 Rumah Miskin" />
|
|
<div role="tabpanel" class="tab-content bg-base-100 border-base-300 rounded-box p-6 shadow-sm">
|
|
@if(auth()->user()->hasRole('administrator'))
|
|
<div id="bulkBar_miskin" class="hidden alert alert-warning py-2 mb-2 flex items-center justify-between">
|
|
<span class="text-sm font-semibold"><span id="bulkCount_miskin">0</span> item terpilih</span>
|
|
<button onclick="bulkDelete('miskin')" class="btn btn-xs btn-error text-white"><i class="fa-solid fa-trash mr-1"></i>Hapus Terpilih</button>
|
|
</div>
|
|
@endif
|
|
<div class="overflow-x-auto">
|
|
<table class="table table-zebra w-full" id="tableMiskin">
|
|
<thead>
|
|
<tr class="text-xs font-bold uppercase opacity-50">
|
|
@if(auth()->user()->hasRole('administrator'))
|
|
<th><input type="checkbox" class="checkbox checkbox-xs" onchange="toggleSelectAll('miskin', this.checked)"></th>
|
|
@endif
|
|
<th>ID</th>
|
|
<th>Nama KK</th>
|
|
<th>KK/Jiwa</th>
|
|
<th>Pekerjaan</th>
|
|
<th>Kondisi</th>
|
|
<th>Koordinat</th>
|
|
<th class="text-center">Aksi</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody id="bodyMiskin"></tbody>
|
|
</table>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</main>
|
|
|
|
<!-- Modal: Edit Rumah Ibadah -->
|
|
<dialog id="modal_edit_ibadah" class="modal">
|
|
<div class="modal-box w-11/12 max-w-lg">
|
|
<h3 class="font-bold text-lg mb-4">
|
|
<i class="fa-solid fa-mosque mr-2 text-success"></i>Edit Rumah Ibadah
|
|
</h3>
|
|
<input type="hidden" id="edit_ib_id">
|
|
<div class="grid grid-cols-2 gap-3">
|
|
<div class="form-control col-span-2">
|
|
<label class="label"><span class="label-text font-bold">Nama *</span></label>
|
|
<input id="edit_ib_nama" type="text" class="input input-bordered w-full" placeholder="Nama rumah ibadah">
|
|
</div>
|
|
<div class="form-control">
|
|
<label class="label"><span class="label-text font-bold">Jenis</span></label>
|
|
<select id="edit_ib_jenis" class="select select-bordered w-full">
|
|
<option value="">Pilih Jenis</option>
|
|
<option value="masjid">Masjid</option>
|
|
<option value="gereja">Gereja</option>
|
|
<option value="pura">Pura</option>
|
|
<option value="klenteng">Klenteng</option>
|
|
<option value="vihara">Vihara</option>
|
|
</select>
|
|
</div>
|
|
<div class="form-control">
|
|
<label class="label"><span class="label-text font-bold">Radius Analisis (m)</span></label>
|
|
<input id="edit_ib_radius" type="number" class="input input-bordered w-full" value="500" min="50" max="2000">
|
|
</div>
|
|
<div class="form-control col-span-2">
|
|
<label class="label"><span class="label-text font-bold">Alamat</span></label>
|
|
<textarea id="edit_ib_alamat" class="textarea textarea-bordered w-full" rows="2" placeholder="Alamat lengkap"></textarea>
|
|
</div>
|
|
<div class="form-control">
|
|
<label class="label"><span class="label-text font-bold">Pengurus</span></label>
|
|
<input id="edit_ib_pengurus" type="text" class="input input-bordered w-full" placeholder="Nama pengurus">
|
|
</div>
|
|
<div class="form-control">
|
|
<label class="label"><span class="label-text font-bold">Telepon</span></label>
|
|
<input id="edit_ib_telepon" type="text" class="input input-bordered w-full" placeholder="No. telepon">
|
|
</div>
|
|
<div class="form-control">
|
|
<label class="label"><span class="label-text font-bold">Jumlah Jemaah</span></label>
|
|
<input id="edit_ib_jemaah" type="number" class="input input-bordered w-full" value="0" min="0">
|
|
</div>
|
|
<div class="form-control col-span-2">
|
|
<label class="label"><span class="label-text font-bold">Keterangan</span></label>
|
|
<textarea id="edit_ib_ket" class="textarea textarea-bordered w-full" rows="2" placeholder="Keterangan tambahan"></textarea>
|
|
</div>
|
|
</div>
|
|
<div class="modal-action">
|
|
<button onclick="document.getElementById('modal_edit_ibadah').close()" class="btn btn-ghost">Batal</button>
|
|
<button onclick="saveEditIbadah()" class="btn btn-success">
|
|
<i class="fa-solid fa-floppy-disk mr-1"></i>Simpan Perubahan
|
|
</button>
|
|
</div>
|
|
</div>
|
|
<form method="dialog" class="modal-backdrop"><button>close</button></form>
|
|
</dialog>
|
|
|
|
<!-- Modal: Edit Keluarga Miskin -->
|
|
<dialog id="modal_edit_miskin" class="modal">
|
|
<div class="modal-box w-11/12 max-w-lg">
|
|
<h3 class="font-bold text-lg mb-4">
|
|
<i class="fa-solid fa-house mr-2 text-error"></i>Edit Keluarga Miskin
|
|
</h3>
|
|
<input type="hidden" id="edit_rm_id">
|
|
<div class="grid grid-cols-2 gap-3">
|
|
<div class="form-control col-span-2">
|
|
<label class="label"><span class="label-text font-bold">Nama Kepala Keluarga</span></label>
|
|
<input id="edit_rm_nama_kk" type="text" class="input input-bordered w-full" placeholder="Nama KK">
|
|
</div>
|
|
<div class="form-control">
|
|
<label class="label"><span class="label-text font-bold">Jumlah KK</span></label>
|
|
<input id="edit_rm_kk" type="number" class="input input-bordered w-full" value="1" min="1">
|
|
</div>
|
|
<div class="form-control">
|
|
<label class="label"><span class="label-text font-bold">Jumlah Jiwa</span></label>
|
|
<input id="edit_rm_jiwa" type="number" class="input input-bordered w-full" value="4" min="1">
|
|
</div>
|
|
<div class="form-control col-span-2">
|
|
<label class="label"><span class="label-text font-bold">Alamat</span></label>
|
|
<textarea id="edit_rm_alamat" class="textarea textarea-bordered w-full" rows="2" placeholder="Alamat lengkap"></textarea>
|
|
</div>
|
|
<div class="form-control">
|
|
<label class="label"><span class="label-text font-bold">Pekerjaan</span></label>
|
|
<input id="edit_rm_kerja" type="text" class="input input-bordered w-full" placeholder="Pekerjaan KK">
|
|
</div>
|
|
<div class="form-control">
|
|
<label class="label"><span class="label-text font-bold">Pendapatan (Rp)</span></label>
|
|
<input id="edit_rm_duit" type="number" class="input input-bordered w-full" value="0" min="0">
|
|
</div>
|
|
<div class="form-control">
|
|
<label class="label"><span class="label-text font-bold">Kondisi Rumah</span></label>
|
|
<select id="edit_rm_kondisi" class="select select-bordered w-full">
|
|
<option value="">Pilih Kondisi</option>
|
|
<option value="sedang">Sedang</option>
|
|
<option value="buruk">Buruk</option>
|
|
<option value="sangat_buruk">Sangat Buruk</option>
|
|
</select>
|
|
</div>
|
|
<div class="form-control col-span-2">
|
|
<label class="label"><span class="label-text font-bold">Keterangan</span></label>
|
|
<textarea id="edit_rm_ket" class="textarea textarea-bordered w-full" rows="2" placeholder="Keterangan tambahan"></textarea>
|
|
</div>
|
|
</div>
|
|
<div class="modal-action">
|
|
<button onclick="document.getElementById('modal_edit_miskin').close()" class="btn btn-ghost">Batal</button>
|
|
<button onclick="saveEditMiskin()" class="btn btn-error">
|
|
<i class="fa-solid fa-floppy-disk mr-1"></i>Simpan Perubahan
|
|
</button>
|
|
</div>
|
|
</div>
|
|
<form method="dialog" class="modal-backdrop"><button>close</button></form>
|
|
</dialog>
|
|
|
|
<!-- Modal: Confirm Delete -->
|
|
<dialog id="deleteModal" class="modal">
|
|
<div class="modal-box max-w-sm text-center">
|
|
<div class="text-5xl mb-4 text-error"><i class="fa-solid fa-triangle-exclamation"></i></div>
|
|
<h3 class="font-bold text-xl mb-2">Hapus Item?</h3>
|
|
<p class="text-sm text-base-content/60 mb-6">Penghapusan tidak dapat dibatalkan.</p>
|
|
<div class="flex gap-2 justify-center">
|
|
<button class="btn btn-ghost px-8" onclick="deleteModal.close()">Batal</button>
|
|
<button class="btn btn-error px-8 text-white" id="confirmDeleteBtn">Konfirmasi Hapus</button>
|
|
</div>
|
|
</div>
|
|
</dialog>
|
|
|
|
<!-- Modal: Import CSV -->
|
|
<dialog id="modal_import" class="modal">
|
|
<div class="modal-box w-11/12 max-w-md">
|
|
<h3 class="font-bold text-lg mb-4"><i class="fa-solid fa-file-import mr-2 text-info"></i>Import Data dari CSV</h3>
|
|
<div class="form-control mb-3">
|
|
<label class="label"><span class="label-text font-bold">Jenis Data</span></label>
|
|
<select id="import_type" class="select select-bordered w-full">
|
|
<option value="miskin">Keluarga Miskin</option>
|
|
<option value="ibadah">Rumah Ibadah</option>
|
|
</select>
|
|
</div>
|
|
<div class="form-control mb-3">
|
|
<label class="label"><span class="label-text font-bold">File CSV</span></label>
|
|
<input id="import_file" type="file" accept=".csv,text/csv" class="file-input file-input-bordered w-full">
|
|
</div>
|
|
<div class="text-xs text-base-content/60 bg-base-200 p-3 rounded">
|
|
<strong>Format kolom (sama dengan hasil Ekspor CSV):</strong><br>
|
|
<span id="import_format_hint"></span>
|
|
</div>
|
|
<div class="modal-action">
|
|
<button onclick="document.getElementById('modal_import').close()" class="btn btn-ghost">Batal</button>
|
|
<button onclick="submitImport()" class="btn btn-info text-white"><i class="fa-solid fa-upload mr-1"></i>Import</button>
|
|
</div>
|
|
</div>
|
|
<form method="dialog" class="modal-backdrop"><button>close</button></form>
|
|
</dialog>
|
|
|
|
<!-- Modal Anggota Keluarga -->
|
|
<dialog id="modal_anggota" class="modal">
|
|
<div class="modal-box w-11/12 max-w-3xl">
|
|
<h3 class="font-bold text-lg mb-1 flex items-center gap-2"><i class="fa-solid fa-users text-info"></i> Anggota Keluarga</h3>
|
|
<p id="anggota_rumah_label" class="text-xs opacity-50 mb-4">-</p>
|
|
<div class="overflow-x-auto mb-4">
|
|
<table class="table table-sm w-full">
|
|
<thead><tr class="text-xs uppercase opacity-50"><th>Nama</th><th>Hubungan</th><th>L/P</th><th>Umur</th><th>Pendidikan</th><th>Status</th><th></th></tr></thead>
|
|
<tbody id="bodyAnggota"><tr><td colspan="7" class="text-center py-4 opacity-30 italic">Memuat...</td></tr></tbody>
|
|
</table>
|
|
</div>
|
|
@if(auth()->user()->hasRole('administrator') || auth()->user()->hasRole('petugas_pendataan'))
|
|
<div class="collapse collapse-arrow border border-base-300 rounded-lg">
|
|
<input type="checkbox" id="toggleFormAnggota">
|
|
<div class="collapse-title text-sm font-bold"><i class="fa-solid fa-plus mr-2 text-info"></i>Tambah Anggota</div>
|
|
<div class="collapse-content">
|
|
<div class="grid grid-cols-2 gap-3 mt-2">
|
|
<div class="form-control col-span-2">
|
|
<label class="label"><span class="label-text font-bold">Nama</span></label>
|
|
<input id="anggota_nama" type="text" class="input input-bordered w-full" placeholder="Nama lengkap">
|
|
</div>
|
|
<div class="form-control">
|
|
<label class="label"><span class="label-text font-bold">Hubungan</span></label>
|
|
<select id="anggota_hubungan" class="select select-bordered w-full">
|
|
<option value="kepala_keluarga">Kepala Keluarga</option>
|
|
<option value="istri">Istri</option>
|
|
<option value="anak">Anak</option>
|
|
<option value="orang_tua">Orang Tua</option>
|
|
<option value="lainnya" selected>Lainnya</option>
|
|
</select>
|
|
</div>
|
|
<div class="form-control">
|
|
<label class="label"><span class="label-text font-bold">Jenis Kelamin</span></label>
|
|
<select id="anggota_kelamin" class="select select-bordered w-full">
|
|
<option value="">-- pilih --</option>
|
|
<option value="laki_laki">Laki-laki</option>
|
|
<option value="perempuan">Perempuan</option>
|
|
</select>
|
|
</div>
|
|
<div class="form-control">
|
|
<label class="label"><span class="label-text font-bold">Tanggal Lahir</span></label>
|
|
<input id="anggota_tgl_lahir" type="date" class="input input-bordered w-full">
|
|
</div>
|
|
<div class="form-control">
|
|
<label class="label"><span class="label-text font-bold">Pendidikan</span></label>
|
|
<select id="anggota_pendidikan" class="select select-bordered w-full">
|
|
<option value="">-- pilih --</option>
|
|
<option value="tidak_sekolah">Tidak Sekolah</option>
|
|
<option value="sd">SD</option>
|
|
<option value="smp">SMP</option>
|
|
<option value="sma">SMA</option>
|
|
<option value="d3">D3</option>
|
|
<option value="s1_plus">S1+</option>
|
|
</select>
|
|
</div>
|
|
<div class="form-control col-span-2">
|
|
<label class="label"><span class="label-text font-bold">Status Hidup</span></label>
|
|
<select id="anggota_status" class="select select-bordered w-full">
|
|
<option value="hidup">Hidup</option>
|
|
<option value="meninggal">Meninggal</option>
|
|
</select>
|
|
</div>
|
|
</div>
|
|
<button onclick="simpanAnggota()" class="btn btn-info btn-sm mt-3 w-full"><i class="fa-solid fa-floppy-disk mr-1"></i>Simpan Anggota</button>
|
|
</div>
|
|
</div>
|
|
@endif
|
|
<div class="modal-action">
|
|
<button onclick="document.getElementById('modal_anggota').close()" class="btn btn-ghost">Tutup</button>
|
|
</div>
|
|
</div>
|
|
<form method="dialog" class="modal-backdrop"><button>close</button></form>
|
|
</dialog>
|
|
|
|
<!-- Modal Riwayat Bantuan -->
|
|
<dialog id="modal_bantuan" class="modal">
|
|
<div class="modal-box w-11/12 max-w-2xl">
|
|
<h3 class="font-bold text-lg mb-1 flex items-center gap-2"><i class="fa-solid fa-hand-holding-heart text-success"></i> Riwayat Bantuan</h3>
|
|
<p id="bantuan_rumah_label" class="text-xs opacity-50 mb-4">-</p>
|
|
<div class="overflow-x-auto mb-4">
|
|
<table class="table table-sm w-full">
|
|
<thead><tr class="text-xs uppercase opacity-50"><th>Tanggal</th><th>Jenis</th><th>Dari Ibadah</th><th>Dicatat oleh</th><th></th></tr></thead>
|
|
<tbody id="bodyBantuan"><tr><td colspan="5" class="text-center py-4 opacity-30 italic">Memuat...</td></tr></tbody>
|
|
</table>
|
|
</div>
|
|
@if(auth()->user()->hasRole('administrator') || auth()->user()->hasRole('petugas_pendataan') || auth()->user()->hasRole('pengurus_ibadah'))
|
|
<div class="collapse collapse-arrow border border-base-300 rounded-lg">
|
|
<input type="checkbox" id="toggleFormBantuan">
|
|
<div class="collapse-title text-sm font-bold"><i class="fa-solid fa-plus mr-2 text-success"></i>Catat Bantuan Baru</div>
|
|
<div class="collapse-content">
|
|
<div class="grid grid-cols-2 gap-3 mt-2">
|
|
<div class="form-control col-span-2">
|
|
<label class="label"><span class="label-text font-bold">Jenis Bantuan</span></label>
|
|
<input id="bantuan_jenis" type="text" class="input input-bordered w-full" placeholder="Contoh: sembako, uang, pakaian">
|
|
</div>
|
|
<div class="form-control">
|
|
<label class="label"><span class="label-text font-bold">Tanggal</span></label>
|
|
<input id="bantuan_tanggal" type="date" class="input input-bordered w-full">
|
|
</div>
|
|
<div class="form-control">
|
|
<label class="label"><span class="label-text font-bold">Dari Rumah Ibadah</span></label>
|
|
<select id="bantuan_ibadah" class="select select-bordered w-full">
|
|
<option value="">-- Pilih (opsional) --</option>
|
|
</select>
|
|
</div>
|
|
<div class="form-control col-span-2">
|
|
<label class="label"><span class="label-text font-bold">Keterangan</span></label>
|
|
<textarea id="bantuan_ket" class="textarea textarea-bordered w-full" rows="2" placeholder="Keterangan tambahan"></textarea>
|
|
</div>
|
|
</div>
|
|
<button onclick="simpanBantuan()" class="btn btn-success btn-sm mt-3 w-full"><i class="fa-solid fa-floppy-disk mr-1"></i>Simpan Bantuan</button>
|
|
</div>
|
|
</div>
|
|
@endif
|
|
<div class="modal-action">
|
|
<button onclick="document.getElementById('modal_bantuan').close()" class="btn btn-ghost">Tutup</button>
|
|
</div>
|
|
</div>
|
|
<form method="dialog" class="modal-backdrop"><button>close</button></form>
|
|
</dialog>
|
|
|
|
<!-- Toast Container -->
|
|
<div class="toast toast-end toast-bottom z-[9999]" id="toastBox"></div>
|
|
|
|
<script>
|
|
const authUserRole = '{{ auth()->user()->getRoleNames()->first() ?? '' }}';
|
|
const authUserId = {{ auth()->id() }};
|
|
const canEditIbadah = ['administrator', 'pengurus_ibadah'].includes(authUserRole);
|
|
const canEditMiskin = ['administrator', 'petugas_pendataan'].includes(authUserRole);
|
|
const csrfToken = document.querySelector('meta[name="csrf-token"]').getAttribute('content');
|
|
const isAdmin = authUserRole === 'administrator';
|
|
let selectedIbadah = new Set();
|
|
let selectedMiskin = new Set();
|
|
|
|
let ibadahData = [], miskinData = [];
|
|
document.addEventListener('DOMContentLoaded', loadAllData);
|
|
const searchInput = document.getElementById('searchInput');
|
|
|
|
async function loadAllData() {
|
|
try {
|
|
const [ri, rm] = await Promise.all([
|
|
fetch('{{ url('/api/ibadah') }}').then(r => r.json()),
|
|
fetch('{{ url('/api/miskin') }}').then(r => r.json())
|
|
]);
|
|
ibadahData = ri;
|
|
miskinData = rm;
|
|
renderTables();
|
|
} catch (e) { showToast('Gagal memuat data', 'error'); }
|
|
}
|
|
|
|
function renderTables(filteredI = null, filteredM = null) {
|
|
const dataI = filteredI || ibadahData;
|
|
const dataM = filteredM || miskinData;
|
|
|
|
// Stats
|
|
document.getElementById('countIbadah').innerText = ibadahData.length;
|
|
document.getElementById('countMiskin').innerText = miskinData.length;
|
|
document.getElementById('countJiwa').innerText = miskinData.reduce((acc, h) => acc + parseInt(h.jumlah_orang), 0);
|
|
|
|
// Ibadah Table
|
|
const bodyI = document.getElementById('bodyIbadah');
|
|
bodyI.innerHTML = dataI.length ? dataI.map((item, i) => {
|
|
const canEditThis = canEditIbadah && (authUserRole === 'administrator' || item.user_id === authUserId);
|
|
return `
|
|
<tr>
|
|
${isAdmin ? `<td><input type="checkbox" class="checkbox checkbox-xs" ${selectedIbadah.has(String(item.id)) ? 'checked' : ''} onchange="toggleSelect('ibadah', '${item.id}', this.checked)"></td>` : ''}
|
|
<td class="text-xs font-bold opacity-30">${i + 1}</td>
|
|
<td>
|
|
<div class="font-bold text-primary">${item.nama}</div>
|
|
<div class="text-[10px] opacity-50 truncate max-w-[200px]">${item.alamat}</div>
|
|
</td>
|
|
<td class="capitalize text-xs">${item.jenis || '-'}</td>
|
|
<td class="text-xs">
|
|
<div class="font-semibold">${item.pengurus || '-'}</div>
|
|
<div class="opacity-50">${item.telepon || '-'}</div>
|
|
</td>
|
|
<td><span class="badge badge-outline badge-sm">${item.radius}m</span></td>
|
|
<td class="font-mono text-[10px]">${parseFloat(item.latitude).toFixed(4)}, ${parseFloat(item.longitude).toFixed(4)}</td>
|
|
<td class="text-center">
|
|
<div class="flex gap-1 justify-center">
|
|
${canEditThis ? `<button class="btn btn-xs btn-warning btn-outline" onclick="openEditIbadahById(${item.id})"><i class="fa-solid fa-pen"></i></button>` : ''}
|
|
${canEditThis ? `<button class="btn btn-xs btn-error btn-outline" onclick="prepDelete('ibadah', ${item.id})"><i class="fa-solid fa-trash"></i></button>` : ''}
|
|
</div>
|
|
</td>
|
|
</tr>`;
|
|
}).join('') : '<tr><td colspan="8" class="text-center py-10 opacity-30 italic">Belum ada data</td></tr>';
|
|
|
|
// Miskin Table
|
|
const bodyM = document.getElementById('bodyMiskin');
|
|
bodyM.innerHTML = dataM.length ? dataM.map(item => `
|
|
<tr>
|
|
${isAdmin ? `<td><input type="checkbox" class="checkbox checkbox-xs" ${selectedMiskin.has(String(item.id_rumah)) ? 'checked' : ''} onchange="toggleSelect('miskin', '${item.id_rumah}', this.checked)"></td>` : ''}
|
|
<td class="font-bold text-error">${item.id_rumah}</td>
|
|
<td>
|
|
<div class="font-bold">${item.nama_kk || '-'}</div>
|
|
<div class="text-[10px] opacity-50 truncate max-w-[200px]">${item.alamat || '-'}</div>
|
|
</td>
|
|
<td>
|
|
<div class="badge badge-warning badge-xs">${item.jumlah_kk} KK</div>
|
|
<div class="badge badge-info badge-xs">${item.jumlah_orang} Jiwa</div>
|
|
</td>
|
|
<td class="text-xs">${item.pekerjaan || '-'}</td>
|
|
<td class="capitalize text-xs">${item.kondisi_rumah || '-'}</td>
|
|
<td class="font-mono text-[10px]">${parseFloat(item.latitude).toFixed(4)}, ${parseFloat(item.longitude).toFixed(4)}</td>
|
|
<td class="text-center">
|
|
<div class="flex gap-1 justify-center flex-wrap">
|
|
<button class="btn btn-xs btn-info btn-outline" onclick="openAnggotaModal('${item.id_rumah}')" title="Anggota Keluarga"><i class="fa-solid fa-users"></i></button>
|
|
<button class="btn btn-xs btn-success btn-outline" onclick="openBantuanModal('${item.id_rumah}')" title="Riwayat Bantuan"><i class="fa-solid fa-hand-holding-heart"></i></button>
|
|
${canEditMiskin ? `<button class="btn btn-xs btn-warning btn-outline" onclick="openEditMiskinById('${item.id_rumah}')"><i class="fa-solid fa-pen"></i></button>` : ''}
|
|
${canEditMiskin ? `<button class="btn btn-xs btn-error btn-outline" onclick="prepDelete('miskin', '${item.id_rumah}')"><i class="fa-solid fa-trash"></i></button>` : ''}
|
|
</div>
|
|
</td>
|
|
</tr>
|
|
`).join('') : '<tr><td colspan="8" class="text-center py-10 opacity-30 italic">Belum ada data</td></tr>';
|
|
}
|
|
|
|
function filterData() {
|
|
const query = searchInput.value.toLowerCase();
|
|
const filteredI = ibadahData.filter(i =>
|
|
i.nama.toLowerCase().includes(query) ||
|
|
(i.alamat && i.alamat.toLowerCase().includes(query)) ||
|
|
(i.pengurus && i.pengurus.toLowerCase().includes(query))
|
|
);
|
|
const filteredM = miskinData.filter(m =>
|
|
(m.nama_kk && m.nama_kk.toLowerCase().includes(query)) ||
|
|
(m.alamat && m.alamat.toLowerCase().includes(query)) ||
|
|
m.id_rumah.toLowerCase().includes(query)
|
|
);
|
|
renderTables(filteredI, filteredM);
|
|
}
|
|
|
|
function exportCSV() {
|
|
const activeTab = document.querySelector('input[name="data_tabs"]:checked').getAttribute('aria-label');
|
|
let csvContent = "data:text/csv;charset=utf-8,";
|
|
|
|
if (activeTab.includes("Ibadah")) {
|
|
csvContent += "ID,Nama,Jenis,Alamat,Radius,Jemaah,Pengurus,Telepon,Lat,Lon\n";
|
|
ibadahData.forEach(i => {
|
|
csvContent += `${i.id},"${i.nama}","${i.jenis}","${i.alamat}",${i.radius},${i.jumlah_jemaah},"${i.pengurus}","${i.telepon}",${i.latitude},${i.longitude}\n`;
|
|
});
|
|
downloadFile(csvContent, "data_rumah_ibadah.csv");
|
|
} else {
|
|
csvContent += "ID Rumah,Nama KK,Alamat,KK,Jiwa,Pekerjaan,Pendapatan,Kondisi,Lat,Lon\n";
|
|
miskinData.forEach(m => {
|
|
csvContent += `"${m.id_rumah}","${m.nama_kk}","${m.alamat}",${m.jumlah_kk},${m.jumlah_orang},"${m.pekerjaan}",${m.pendapatan},"${m.kondisi_rumah}",${m.latitude},${m.longitude}\n`;
|
|
});
|
|
downloadFile(csvContent, "data_rumah_miskin.csv");
|
|
}
|
|
}
|
|
|
|
function downloadFile(content, filename) {
|
|
const encodedUri = encodeURI(content);
|
|
const link = document.createElement("a");
|
|
link.setAttribute("href", encodedUri);
|
|
link.setAttribute("download", filename);
|
|
document.body.appendChild(link);
|
|
link.click();
|
|
document.body.removeChild(link);
|
|
}
|
|
|
|
// ── EDIT FORMS ──────────────────────────────────────────────
|
|
function openEditIbadahById(id) {
|
|
const item = ibadahData.find(i => i.id === id);
|
|
if (!item) return;
|
|
document.getElementById('edit_ib_id').value = item.id;
|
|
document.getElementById('edit_ib_nama').value = item.nama || '';
|
|
document.getElementById('edit_ib_jenis').value = item.jenis || '';
|
|
document.getElementById('edit_ib_alamat').value = item.alamat || '';
|
|
document.getElementById('edit_ib_pengurus').value = item.pengurus || '';
|
|
document.getElementById('edit_ib_telepon').value = item.telepon || '';
|
|
document.getElementById('edit_ib_jemaah').value = item.jumlah_jemaah || 0;
|
|
document.getElementById('edit_ib_radius').value = item.radius || 500;
|
|
document.getElementById('edit_ib_ket').value = item.keterangan || '';
|
|
document.getElementById('modal_edit_ibadah').showModal();
|
|
}
|
|
|
|
async function saveEditIbadah() {
|
|
const id = document.getElementById('edit_ib_id').value;
|
|
const item = ibadahData.find(i => i.id == id);
|
|
if (!item) return;
|
|
|
|
const body = {
|
|
nama: document.getElementById('edit_ib_nama').value,
|
|
jenis: document.getElementById('edit_ib_jenis').value,
|
|
alamat: document.getElementById('edit_ib_alamat').value,
|
|
pengurus: document.getElementById('edit_ib_pengurus').value,
|
|
telepon: document.getElementById('edit_ib_telepon').value,
|
|
jumlah_jemaah: parseInt(document.getElementById('edit_ib_jemaah').value) || 0,
|
|
radius: parseInt(document.getElementById('edit_ib_radius').value) || 500,
|
|
keterangan: document.getElementById('edit_ib_ket').value,
|
|
latitude: item.latitude,
|
|
longitude: item.longitude,
|
|
};
|
|
|
|
const res = await fetch(`{{ url('/api/ibadah') }}/${id}`, {
|
|
method: 'PUT',
|
|
headers: { 'Content-Type': 'application/json', 'X-CSRF-TOKEN': csrfToken },
|
|
body: JSON.stringify(body),
|
|
});
|
|
|
|
const resJson = await res.json();
|
|
if (resJson.success) {
|
|
document.getElementById('modal_edit_ibadah').close();
|
|
showToast('Data berhasil diperbarui', 'success');
|
|
await loadAllData();
|
|
} else if (res.status === 403) {
|
|
showToast(resJson.message || 'Tidak diizinkan mengubah data ini', 'error');
|
|
} else {
|
|
showToast('Gagal memperbarui data', 'error');
|
|
}
|
|
}
|
|
|
|
function openEditMiskinById(idRumah) {
|
|
const item = miskinData.find(i => i.id_rumah === idRumah);
|
|
if (!item) return;
|
|
document.getElementById('edit_rm_id').value = item.id_rumah;
|
|
document.getElementById('edit_rm_nama_kk').value = item.nama_kk || '';
|
|
document.getElementById('edit_rm_kk').value = item.jumlah_kk || 1;
|
|
document.getElementById('edit_rm_jiwa').value = item.jumlah_orang || 4;
|
|
document.getElementById('edit_rm_alamat').value = item.alamat || '';
|
|
document.getElementById('edit_rm_kerja').value = item.pekerjaan || '';
|
|
document.getElementById('edit_rm_duit').value = item.pendapatan || 0;
|
|
document.getElementById('edit_rm_kondisi').value = item.kondisi_rumah || '';
|
|
document.getElementById('edit_rm_ket').value = item.keterangan || '';
|
|
document.getElementById('modal_edit_miskin').showModal();
|
|
}
|
|
|
|
async function saveEditMiskin() {
|
|
const idRumah = document.getElementById('edit_rm_id').value;
|
|
const item = miskinData.find(i => i.id_rumah === idRumah);
|
|
if (!item) return;
|
|
|
|
const body = {
|
|
nama_kk: document.getElementById('edit_rm_nama_kk').value,
|
|
jumlah_kk: parseInt(document.getElementById('edit_rm_kk').value) || 1,
|
|
jumlah_orang: parseInt(document.getElementById('edit_rm_jiwa').value) || 1,
|
|
alamat: document.getElementById('edit_rm_alamat').value,
|
|
pekerjaan: document.getElementById('edit_rm_kerja').value,
|
|
pendapatan: parseInt(document.getElementById('edit_rm_duit').value) || 0,
|
|
kondisi_rumah: document.getElementById('edit_rm_kondisi').value,
|
|
keterangan: document.getElementById('edit_rm_ket').value,
|
|
latitude: item.latitude,
|
|
longitude: item.longitude,
|
|
};
|
|
|
|
const res = await fetch(`{{ url('/api/miskin') }}/${idRumah}`, {
|
|
method: 'PUT',
|
|
headers: { 'Content-Type': 'application/json', 'X-CSRF-TOKEN': csrfToken },
|
|
body: JSON.stringify(body),
|
|
});
|
|
|
|
const resJson = await res.json();
|
|
if (resJson.success) {
|
|
document.getElementById('modal_edit_miskin').close();
|
|
showToast('Data berhasil diperbarui', 'success');
|
|
await loadAllData();
|
|
} else {
|
|
showToast('Gagal memperbarui data', 'error');
|
|
}
|
|
}
|
|
|
|
// ── CSV IMPORT ──────────────────────────────────────────────
|
|
const importHints = {
|
|
miskin: 'ID Rumah, Nama KK, Alamat, KK, Jiwa, Pekerjaan, Pendapatan, Kondisi, Lat, Lon',
|
|
ibadah: 'ID (diabaikan), Nama, Jenis, Alamat, Radius, Jemaah, Pengurus, Telepon, Lat, Lon',
|
|
};
|
|
document.getElementById('import_type')?.addEventListener('change', e => {
|
|
document.getElementById('import_format_hint').textContent = importHints[e.target.value];
|
|
});
|
|
document.addEventListener('DOMContentLoaded', () => {
|
|
const h = document.getElementById('import_format_hint');
|
|
if (h) h.textContent = importHints.miskin;
|
|
});
|
|
|
|
async function submitImport() {
|
|
const type = document.getElementById('import_type').value;
|
|
const fileInput = document.getElementById('import_file');
|
|
if (!fileInput.files.length) { showToast('Pilih file CSV dulu', 'error'); return; }
|
|
|
|
const fd = new FormData();
|
|
fd.append('file', fileInput.files[0]);
|
|
|
|
const res = await fetch(`{{ url('/api/admin/import') }}/${type}`, {
|
|
method: 'POST',
|
|
headers: { 'X-CSRF-TOKEN': csrfToken },
|
|
body: fd,
|
|
});
|
|
const json = await res.json();
|
|
if (json.success) {
|
|
document.getElementById('modal_import').close();
|
|
fileInput.value = '';
|
|
showToast(`Import selesai: ${json.imported} berhasil, ${json.failed} gagal`, 'success');
|
|
loadAllData();
|
|
} else {
|
|
showToast(json.message || 'Import gagal', 'error');
|
|
}
|
|
}
|
|
|
|
// ── BULK DELETE ─────────────────────────────────────────────
|
|
function toggleSelect(type, id, checked) {
|
|
const set = type === 'ibadah' ? selectedIbadah : selectedMiskin;
|
|
if (checked) set.add(String(id)); else set.delete(String(id));
|
|
updateBulkBar(type);
|
|
}
|
|
|
|
function toggleSelectAll(type, checked) {
|
|
const set = type === 'ibadah' ? selectedIbadah : selectedMiskin;
|
|
set.clear();
|
|
if (checked) {
|
|
(type === 'ibadah' ? ibadahData : miskinData).forEach(it =>
|
|
set.add(String(type === 'ibadah' ? it.id : it.id_rumah)));
|
|
}
|
|
renderTables();
|
|
updateBulkBar(type);
|
|
}
|
|
|
|
function updateBulkBar(type) {
|
|
const set = type === 'ibadah' ? selectedIbadah : selectedMiskin;
|
|
const bar = document.getElementById(`bulkBar_${type}`);
|
|
if (!bar) return;
|
|
bar.classList.toggle('hidden', set.size === 0);
|
|
document.getElementById(`bulkCount_${type}`).textContent = set.size;
|
|
}
|
|
|
|
async function bulkDelete(type) {
|
|
const set = type === 'ibadah' ? selectedIbadah : selectedMiskin;
|
|
const ids = [...set];
|
|
if (!ids.length) return;
|
|
if (!confirm(`Hapus ${ids.length} data terpilih secara permanen?`)) return;
|
|
|
|
const res = await fetch(`{{ url('/api/admin') }}/${type}/bulk-delete`, {
|
|
method: 'POST',
|
|
headers: { 'Content-Type': 'application/json', 'X-CSRF-TOKEN': csrfToken },
|
|
body: JSON.stringify({ ids }),
|
|
});
|
|
const json = await res.json();
|
|
if (json.success) {
|
|
showToast(`${json.deleted} data dihapus`, 'success');
|
|
set.clear();
|
|
updateBulkBar(type);
|
|
loadAllData();
|
|
} else {
|
|
showToast('Gagal hapus massal', 'error');
|
|
}
|
|
}
|
|
|
|
let deleteTarget = null;
|
|
function prepDelete(type, id) {
|
|
deleteTarget = { type, id };
|
|
deleteModal.showModal();
|
|
}
|
|
|
|
document.getElementById('confirmDeleteBtn').onclick = async () => {
|
|
const { type, id } = deleteTarget;
|
|
const res = await fetch(`{{ url('/api') }}/${type}/${id}`, {
|
|
method: 'DELETE',
|
|
headers: { 'Content-Type': 'application/json', 'X-CSRF-TOKEN': csrfToken }
|
|
});
|
|
const resJson = await res.json();
|
|
if (resJson.success) {
|
|
showToast('Data dihapus', 'success');
|
|
deleteModal.close();
|
|
loadAllData();
|
|
} else showToast('Gagal menghapus', 'error');
|
|
};
|
|
|
|
function showToast(msg, type = 'info') {
|
|
const t = document.createElement('div');
|
|
const alertCls = type === 'success' ? 'alert-success' : type === 'error' ? 'alert-error' : 'alert-info';
|
|
t.className = `alert ${alertCls} shadow-lg py-3 text-xs font-bold`;
|
|
t.innerHTML = `<span><i class="fa-solid fa-circle-info mr-2"></i>${msg}</span>`;
|
|
document.getElementById('toastBox').appendChild(t);
|
|
setTimeout(() => t.remove(), 3000);
|
|
}
|
|
|
|
// ── ANGGOTA KELUARGA ───────────────────────────────────────────────────
|
|
let currentAnggotaRumah = null;
|
|
const canEditAnggota = {{ auth()->user()->hasRole('administrator') || auth()->user()->hasRole('petugas_pendataan') ? 'true' : 'false' }};
|
|
|
|
async function openAnggotaModal(idRumah) {
|
|
currentAnggotaRumah = idRumah;
|
|
const rm = miskinData.find(m => m.id_rumah === idRumah);
|
|
document.getElementById('anggota_rumah_label').textContent = `ID: ${idRumah} — ${rm?.nama_kk || '-'}`;
|
|
document.getElementById('modal_anggota').showModal();
|
|
await loadAnggota(idRumah);
|
|
}
|
|
|
|
async function loadAnggota(idRumah) {
|
|
const res = await fetch(`{{ url('/api/miskin') }}/${encodeURIComponent(idRumah)}/anggota`, { headers: { 'X-CSRF-TOKEN': csrfToken } });
|
|
const list = await res.json();
|
|
document.getElementById('bodyAnggota').innerHTML = list.length ? list.map(a => `
|
|
<tr>
|
|
<td class="font-semibold">${esc(a.nama)}</td>
|
|
<td class="capitalize text-xs">${esc(a.hubungan.replace('_', ' '))}</td>
|
|
<td class="text-xs">${a.jenis_kelamin === 'laki_laki' ? 'L' : a.jenis_kelamin === 'perempuan' ? 'P' : '-'}</td>
|
|
<td class="text-xs">${a.umur !== null ? esc(String(a.umur)) + ' th' : '-'}</td>
|
|
<td class="capitalize text-xs">${a.pendidikan ? esc(a.pendidikan.replace(/_/g, ' ')) : '-'}</td>
|
|
<td><span class="badge badge-xs ${a.status_hidup === 'hidup' ? 'badge-success' : 'badge-error'}">${esc(a.status_hidup)}</span></td>
|
|
<td>${canEditAnggota ? `<button onclick="deleteAnggota(${a.id})" class="btn btn-xs btn-ghost text-error"><i class="fa-solid fa-trash"></i></button>` : ''}</td>
|
|
</tr>`).join('')
|
|
: '<tr><td colspan="7" class="text-center py-4 opacity-30 italic">Belum ada anggota</td></tr>';
|
|
}
|
|
|
|
async function simpanAnggota() {
|
|
const body = {
|
|
nama: document.getElementById('anggota_nama').value.trim(),
|
|
hubungan: document.getElementById('anggota_hubungan').value,
|
|
jenis_kelamin: document.getElementById('anggota_kelamin').value || null,
|
|
tanggal_lahir: document.getElementById('anggota_tgl_lahir').value || null,
|
|
pendidikan: document.getElementById('anggota_pendidikan').value || null,
|
|
status_hidup: document.getElementById('anggota_status').value,
|
|
};
|
|
if (!body.nama) { showToast('Nama wajib diisi', 'error'); return; }
|
|
const res = await fetch(`{{ url('/api/miskin') }}/${encodeURIComponent(currentAnggotaRumah)}/anggota`, {
|
|
method: 'POST',
|
|
headers: { 'Content-Type': 'application/json', 'X-CSRF-TOKEN': csrfToken },
|
|
body: JSON.stringify(body),
|
|
});
|
|
const json = await res.json();
|
|
if (json.success) { showToast('Anggota ditambahkan', 'success'); loadAnggota(currentAnggotaRumah); }
|
|
else showToast('Gagal menambah anggota', 'error');
|
|
}
|
|
|
|
async function deleteAnggota(id) {
|
|
if (!confirm('Hapus anggota ini?')) return;
|
|
const res = await fetch(`{{ url('/api/miskin') }}/${encodeURIComponent(currentAnggotaRumah)}/anggota/${id}`, {
|
|
method: 'DELETE', headers: { 'X-CSRF-TOKEN': csrfToken },
|
|
});
|
|
const json = await res.json();
|
|
if (json.success) { showToast('Anggota dihapus', 'success'); loadAnggota(currentAnggotaRumah); }
|
|
else showToast('Gagal hapus', 'error');
|
|
}
|
|
|
|
// ── RIWAYAT BANTUAN ────────────────────────────────────────────────────
|
|
let currentBantuanRumah = null;
|
|
const canDeleteBantuan = {{ auth()->user()->hasRole('administrator') ? 'true' : 'false' }};
|
|
|
|
async function openBantuanModal(idRumah) {
|
|
currentBantuanRumah = idRumah;
|
|
const rm = miskinData.find(m => m.id_rumah === idRumah);
|
|
document.getElementById('bantuan_rumah_label').textContent = `ID: ${idRumah} — ${rm?.nama_kk || '-'}`;
|
|
|
|
const sel = document.getElementById('bantuan_ibadah');
|
|
if (sel) {
|
|
sel.innerHTML = '<option value="">-- Pilih (opsional) --</option>' +
|
|
ibadahData.map(ib => `<option value="${ib.id}">${esc(ib.nama)}</option>`).join('');
|
|
}
|
|
|
|
const tgl = document.getElementById('bantuan_tanggal');
|
|
if (tgl) tgl.value = new Date().toISOString().split('T')[0];
|
|
|
|
document.getElementById('modal_bantuan').showModal();
|
|
await loadBantuan(idRumah);
|
|
}
|
|
|
|
async function loadBantuan(idRumah) {
|
|
const res = await fetch(`{{ url('/api/miskin') }}/${encodeURIComponent(idRumah)}/bantuan`, { headers: { 'X-CSRF-TOKEN': csrfToken } });
|
|
const list = await res.json();
|
|
document.getElementById('bodyBantuan').innerHTML = list.length ? list.map(b => `
|
|
<tr>
|
|
<td class="text-xs whitespace-nowrap">${esc(b.tanggal_bantuan)}</td>
|
|
<td class="font-semibold text-sm">${esc(b.jenis_bantuan)}</td>
|
|
<td class="text-xs">${esc(b.ibadah_nama || '-')}</td>
|
|
<td class="text-xs">${esc(b.pencatat_nama)}</td>
|
|
<td>${canDeleteBantuan ? `<button onclick="deleteBantuan(${b.id})" class="btn btn-xs btn-ghost text-error"><i class="fa-solid fa-trash"></i></button>` : ''}</td>
|
|
</tr>`).join('')
|
|
: '<tr><td colspan="5" class="text-center py-4 opacity-30 italic">Belum ada riwayat bantuan</td></tr>';
|
|
}
|
|
|
|
async function simpanBantuan() {
|
|
const body = {
|
|
jenis_bantuan: document.getElementById('bantuan_jenis').value.trim(),
|
|
tanggal_bantuan: document.getElementById('bantuan_tanggal').value,
|
|
keterangan: document.getElementById('bantuan_ket').value.trim() || null,
|
|
ibadah_id: document.getElementById('bantuan_ibadah').value || null,
|
|
};
|
|
if (!body.jenis_bantuan || !body.tanggal_bantuan) { showToast('Jenis bantuan dan tanggal wajib diisi', 'error'); return; }
|
|
const res = await fetch(`{{ url('/api/miskin') }}/${encodeURIComponent(currentBantuanRumah)}/bantuan`, {
|
|
method: 'POST',
|
|
headers: { 'Content-Type': 'application/json', 'X-CSRF-TOKEN': csrfToken },
|
|
body: JSON.stringify(body),
|
|
});
|
|
const json = await res.json();
|
|
if (json.success) { showToast('Bantuan dicatat', 'success'); loadBantuan(currentBantuanRumah); }
|
|
else showToast('Gagal menyimpan bantuan', 'error');
|
|
}
|
|
|
|
async function deleteBantuan(id) {
|
|
if (!confirm('Hapus riwayat bantuan ini?')) return;
|
|
const res = await fetch(`{{ url('/api/miskin') }}/${encodeURIComponent(currentBantuanRumah)}/bantuan/${id}`, {
|
|
method: 'DELETE', headers: { 'X-CSRF-TOKEN': csrfToken },
|
|
});
|
|
const json = await res.json();
|
|
if (json.success) { showToast('Bantuan dihapus', 'success'); loadBantuan(currentBantuanRumah); }
|
|
else showToast('Gagal hapus', 'error');
|
|
}
|
|
|
|
// ── NOTIFIKASI PERUBAHAN DATA ────────────────────────────────────────
|
|
(function initNotifPolling() {
|
|
const POLL_MS = 30000;
|
|
const KEY_IBADAH = 'notif_latest_ibadah';
|
|
const KEY_MISKIN = 'notif_latest_miskin';
|
|
|
|
async function pollNotifications() {
|
|
try {
|
|
const since = localStorage.getItem(KEY_IBADAH) || new Date(0).toISOString();
|
|
const res = await fetch(
|
|
`{{ url('/api/notifications') }}?since=${encodeURIComponent(since)}`,
|
|
{ headers: { 'X-CSRF-TOKEN': csrfToken } }
|
|
);
|
|
if (!res.ok) return;
|
|
const data = await res.json();
|
|
|
|
if (data.new_ibadah > 0 || data.new_miskin > 0) {
|
|
if (data.new_ibadah > 0) showToast(`${data.new_ibadah} rumah ibadah baru ditambahkan`, 'info');
|
|
if (data.new_miskin > 0) showToast(`${data.new_miskin} keluarga miskin baru ditambahkan`, 'info');
|
|
const fn = typeof loadAllData !== 'undefined' ? loadAllData
|
|
: typeof loadData !== 'undefined' ? loadData
|
|
: null;
|
|
if (fn) fn();
|
|
}
|
|
if (data.latest_ibadah) localStorage.setItem(KEY_IBADAH, data.latest_ibadah);
|
|
if (data.latest_miskin) localStorage.setItem(KEY_MISKIN, data.latest_miskin);
|
|
} catch (e) { /* silent */ }
|
|
}
|
|
|
|
document.addEventListener('DOMContentLoaded', () => {
|
|
if (!localStorage.getItem(KEY_IBADAH)) localStorage.setItem(KEY_IBADAH, new Date().toISOString());
|
|
if (!localStorage.getItem(KEY_MISKIN)) localStorage.setItem(KEY_MISKIN, new Date().toISOString());
|
|
setInterval(pollNotifications, POLL_MS);
|
|
});
|
|
})();
|
|
</script>
|
|
</body>
|
|
</html>
|