feat: add auth navbar and role-aware UI to all views
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
+162
-50
@@ -3,7 +3,8 @@
|
|||||||
<head>
|
<head>
|
||||||
<meta charset="utf-8">
|
<meta charset="utf-8">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
<title>Data Tabular - WebGIS Miskin & Ibadah</title>
|
<meta name="csrf-token" content="{{ csrf_token() }}">
|
||||||
|
<title>Data Tabular - WebGIS Pemetaan Kemiskinan</title>
|
||||||
|
|
||||||
<!-- DaisyUI + Tailwind -->
|
<!-- DaisyUI + Tailwind -->
|
||||||
<link href="https://cdn.jsdelivr.net/npm/daisyui@4/dist/full.min.css" rel="stylesheet">
|
<link href="https://cdn.jsdelivr.net/npm/daisyui@4/dist/full.min.css" rel="stylesheet">
|
||||||
@@ -28,7 +29,7 @@
|
|||||||
<div class="flex-1">
|
<div class="flex-1">
|
||||||
<a href="{{ url('/') }}" class="btn btn-ghost text-lg font-bold gap-2 text-primary">
|
<a href="{{ url('/') }}" class="btn btn-ghost text-lg font-bold gap-2 text-primary">
|
||||||
<i class="fa-solid fa-house-chimney-crack"></i>
|
<i class="fa-solid fa-house-chimney-crack"></i>
|
||||||
WebGIS Miskin & Ibadah
|
WebGIS Pemetaan Kemiskinan
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex-none">
|
<div class="flex-none">
|
||||||
@@ -36,6 +37,32 @@
|
|||||||
<li><a href="{{ url('/map') }}" class="font-medium"><i class="fa-solid fa-map"></i> Peta</a></li>
|
<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('/data') }}" class="font-semibold bg-primary/10 text-primary rounded-lg"><i class="fa-solid fa-table-list"></i> Data Tabular</a></li>
|
||||||
</ul>
|
</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>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -51,8 +78,19 @@
|
|||||||
</h1>
|
</h1>
|
||||||
<p class="text-base-content/50 mt-1">Daftar rumah ibadah dan keluarga miskin dalam matriks tabular.</p>
|
<p class="text-base-content/50 mt-1">Daftar rumah ibadah dan keluarga miskin dalam matriks tabular.</p>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex gap-2">
|
<div class="flex flex-wrap gap-2">
|
||||||
<a href="{{ url('/map') }}" class="btn btn-primary shadow-lg"><i class="fa-solid fa-plus"></i> Tambah Data di Peta</a>
|
<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>
|
||||||
|
@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>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -84,9 +122,10 @@
|
|||||||
<thead>
|
<thead>
|
||||||
<tr class="text-xs font-bold uppercase opacity-50">
|
<tr class="text-xs font-bold uppercase opacity-50">
|
||||||
<th>#</th>
|
<th>#</th>
|
||||||
<th>Nama Rumah Ibadah</th>
|
<th>Nama</th>
|
||||||
<th>Alamat</th>
|
<th>Jenis</th>
|
||||||
<th>Radius Analisis</th>
|
<th>Pengurus/Telp</th>
|
||||||
|
<th>Radius</th>
|
||||||
<th>Koordinat</th>
|
<th>Koordinat</th>
|
||||||
<th class="text-center">Aksi</th>
|
<th class="text-center">Aksi</th>
|
||||||
</tr>
|
</tr>
|
||||||
@@ -102,10 +141,11 @@
|
|||||||
<table class="table table-zebra w-full" id="tableMiskin">
|
<table class="table table-zebra w-full" id="tableMiskin">
|
||||||
<thead>
|
<thead>
|
||||||
<tr class="text-xs font-bold uppercase opacity-50">
|
<tr class="text-xs font-bold uppercase opacity-50">
|
||||||
<th>ID Rumah</th>
|
<th>ID</th>
|
||||||
<th>Alamat</th>
|
<th>Nama KK</th>
|
||||||
<th>Jumlah KK</th>
|
<th>KK/Jiwa</th>
|
||||||
<th>Jumlah Jiwa</th>
|
<th>Pekerjaan</th>
|
||||||
|
<th>Kondisi</th>
|
||||||
<th>Koordinat</th>
|
<th>Koordinat</th>
|
||||||
<th class="text-center">Aksi</th>
|
<th class="text-center">Aksi</th>
|
||||||
</tr>
|
</tr>
|
||||||
@@ -134,7 +174,14 @@
|
|||||||
<div class="toast toast-end toast-bottom z-[9999]" id="toastBox"></div>
|
<div class="toast toast-end toast-bottom z-[9999]" id="toastBox"></div>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
|
const authUserRole = '{{ auth()->user()->getRoleNames()->first() ?? '' }}';
|
||||||
|
const canEditIbadah = ['administrator', 'pengurus_ibadah'].includes(authUserRole);
|
||||||
|
const canEditMiskin = ['administrator', 'petugas_pendataan'].includes(authUserRole);
|
||||||
|
const csrfToken = document.querySelector('meta[name="csrf-token"]').getAttribute('content');
|
||||||
|
|
||||||
|
let ibadahData = [], miskinData = [];
|
||||||
document.addEventListener('DOMContentLoaded', loadAllData);
|
document.addEventListener('DOMContentLoaded', loadAllData);
|
||||||
|
const searchInput = document.getElementById('searchInput');
|
||||||
|
|
||||||
async function loadAllData() {
|
async function loadAllData() {
|
||||||
try {
|
try {
|
||||||
@@ -142,45 +189,110 @@
|
|||||||
fetch('{{ url('/api/ibadah') }}').then(r => r.json()),
|
fetch('{{ url('/api/ibadah') }}').then(r => r.json()),
|
||||||
fetch('{{ url('/api/miskin') }}').then(r => r.json())
|
fetch('{{ url('/api/miskin') }}').then(r => r.json())
|
||||||
]);
|
]);
|
||||||
|
ibadahData = ri;
|
||||||
// Stats
|
miskinData = rm;
|
||||||
document.getElementById('countIbadah').innerText = ri.length;
|
renderTables();
|
||||||
document.getElementById('countMiskin').innerText = rm.length;
|
|
||||||
document.getElementById('countJiwa').innerText = rm.reduce((acc, h) => acc + parseInt(h.jumlah_orang), 0);
|
|
||||||
|
|
||||||
// Ibadah Table
|
|
||||||
const bodyI = document.getElementById('bodyIbadah');
|
|
||||||
bodyI.innerHTML = ri.length ? ri.map((item, i) => `
|
|
||||||
<tr>
|
|
||||||
<td class="text-xs font-bold opacity-30">${i + 1}</td>
|
|
||||||
<td class="font-bold text-primary">${item.nama}</td>
|
|
||||||
<td class="text-sm opacity-70 max-w-xs truncate">${item.alamat}</td>
|
|
||||||
<td><span class="badge badge-outline badge-sm">${item.radius}m</span></td>
|
|
||||||
<td class="font-mono text-[10px]">${parseFloat(item.latitude).toFixed(6)}, ${parseFloat(item.longitude).toFixed(6)}</td>
|
|
||||||
<td class="text-center">
|
|
||||||
<button class="btn btn-xs btn-error btn-outline" onclick="prepDelete('ibadah', ${item.id})"><i class="fa-solid fa-trash"></i></button>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
`).join('') : '<tr><td colspan="6" class="text-center py-10 opacity-30 italic">Belum ada data</td></tr>';
|
|
||||||
|
|
||||||
// Miskin Table
|
|
||||||
const bodyM = document.getElementById('bodyMiskin');
|
|
||||||
bodyM.innerHTML = rm.length ? rm.map(item => `
|
|
||||||
<tr>
|
|
||||||
<td class="font-bold text-error">${item.id_rumah}</td>
|
|
||||||
<td class="text-xs opacity-70 max-w-xs truncate">${item.alamat || '-'}</td>
|
|
||||||
<td><span class="badge badge-warning badge-sm">${item.jumlah_kk} KK</span></td>
|
|
||||||
<td><span class="badge badge-info badge-sm">${item.jumlah_orang} Jiwa</span></td>
|
|
||||||
<td class="font-mono text-[10px]">${parseFloat(item.latitude).toFixed(6)}, ${parseFloat(item.longitude).toFixed(6)}</td>
|
|
||||||
<td class="text-center">
|
|
||||||
<button class="btn btn-xs btn-error btn-outline" onclick="prepDelete('miskin', '${item.id_rumah}')"><i class="fa-solid fa-trash"></i></button>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
`).join('') : '<tr><td colspan="6" class="text-center py-10 opacity-30 italic">Belum ada data</td></tr>';
|
|
||||||
|
|
||||||
} catch (e) { showToast('Gagal memuat data', 'error'); }
|
} 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) => `
|
||||||
|
<tr>
|
||||||
|
<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">
|
||||||
|
${canEditIbadah ? `<button class="btn btn-xs btn-error btn-outline" onclick="prepDelete('ibadah', ${item.id})"><i class="fa-solid fa-trash"></i></button>` : ''}
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
`).join('') : '<tr><td colspan="7" 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>
|
||||||
|
<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">
|
||||||
|
${canEditMiskin ? `<button class="btn btn-xs btn-error btn-outline" onclick="prepDelete('miskin', '${item.id_rumah}')"><i class="fa-solid fa-trash"></i></button>` : ''}
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
`).join('') : '<tr><td colspan="7" 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);
|
||||||
|
}
|
||||||
|
|
||||||
let deleteTarget = null;
|
let deleteTarget = null;
|
||||||
function prepDelete(type, id) {
|
function prepDelete(type, id) {
|
||||||
deleteTarget = { type, id };
|
deleteTarget = { type, id };
|
||||||
@@ -189,9 +301,9 @@
|
|||||||
|
|
||||||
document.getElementById('confirmDeleteBtn').onclick = async () => {
|
document.getElementById('confirmDeleteBtn').onclick = async () => {
|
||||||
const { type, id } = deleteTarget;
|
const { type, id } = deleteTarget;
|
||||||
const res = await fetch(`{{ url('/api') }}/${type}/${id}`, {
|
const res = await fetch(`{{ url('/api') }}/${type}/${id}`, {
|
||||||
method: 'DELETE',
|
method: 'DELETE',
|
||||||
headers: { 'Content-Type': 'application/json' }
|
headers: { 'Content-Type': 'application/json', 'X-CSRF-TOKEN': csrfToken }
|
||||||
});
|
});
|
||||||
const resJson = await res.json();
|
const resJson = await res.json();
|
||||||
if (resJson.success) {
|
if (resJson.success) {
|
||||||
@@ -205,7 +317,7 @@
|
|||||||
const t = document.createElement('div');
|
const t = document.createElement('div');
|
||||||
const alertCls = type === 'success' ? 'alert-success' : type === 'error' ? 'alert-error' : 'alert-info';
|
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.className = `alert ${alertCls} shadow-lg py-3 text-xs font-bold`;
|
||||||
t.innerHTML = `<span>${msg}</span>`;
|
t.innerHTML = `<span><i class="fa-solid fa-circle-info mr-2"></i>${msg}</span>`;
|
||||||
document.getElementById('toastBox').appendChild(t);
|
document.getElementById('toastBox').appendChild(t);
|
||||||
setTimeout(() => t.remove(), 3000);
|
setTimeout(() => t.remove(), 3000);
|
||||||
}
|
}
|
||||||
|
|||||||
+166
-44
@@ -3,7 +3,8 @@
|
|||||||
<head>
|
<head>
|
||||||
<meta charset="utf-8">
|
<meta charset="utf-8">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
<title>Peta Interaktif - WebGIS Miskin & Ibadah</title>
|
<meta name="csrf-token" content="{{ csrf_token() }}">
|
||||||
|
<title>Peta Interaktif - WebGIS Pemetaan Kemiskinan</title>
|
||||||
|
|
||||||
<!-- Leaflet -->
|
<!-- Leaflet -->
|
||||||
<link rel="stylesheet" href="https://unpkg.com/leaflet@1.9.4/dist/leaflet.css">
|
<link rel="stylesheet" href="https://unpkg.com/leaflet@1.9.4/dist/leaflet.css">
|
||||||
@@ -72,7 +73,7 @@
|
|||||||
<div class="flex-1">
|
<div class="flex-1">
|
||||||
<a href="{{ url('/') }}" class="btn btn-ghost text-lg font-bold gap-2 text-primary">
|
<a href="{{ url('/') }}" class="btn btn-ghost text-lg font-bold gap-2 text-primary">
|
||||||
<i class="fa-solid fa-house-chimney-crack"></i>
|
<i class="fa-solid fa-house-chimney-crack"></i>
|
||||||
WebGIS Miskin & Ibadah
|
WebGIS Pemetaan Kemiskinan
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex-none">
|
<div class="flex-none">
|
||||||
@@ -88,6 +89,32 @@
|
|||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</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>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -177,17 +204,49 @@
|
|||||||
</h3>
|
</h3>
|
||||||
<div class="form-control gap-3">
|
<div class="form-control gap-3">
|
||||||
<div class="bg-base-200 p-2 rounded text-xs font-mono" id="ib_coords">Lat: -, Lng: -</div>
|
<div class="bg-base-200 p-2 rounded text-xs font-mono" id="ib_coords">Lat: -, Lng: -</div>
|
||||||
<label class="form-control w-full">
|
<div class="grid grid-cols-2 gap-3">
|
||||||
<div class="label"><span class="label-text font-bold">Nama Rumah Ibadah</span></div>
|
<label class="form-control w-full">
|
||||||
<input type="text" id="ib_nama" placeholder="Contoh: Masjid Nurul Iman" class="input input-bordered w-full" />
|
<div class="label"><span class="label-text font-bold">Nama Rumah Ibadah</span></div>
|
||||||
</label>
|
<input type="text" id="ib_nama" placeholder="Contoh: Masjid Nurul Iman" class="input input-bordered w-full" />
|
||||||
|
</label>
|
||||||
|
<label class="form-control w-full">
|
||||||
|
<div class="label"><span class="label-text font-bold">Jenis</span></div>
|
||||||
|
<select id="ib_jenis" class="select select-bordered w-full">
|
||||||
|
<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>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
<label class="form-control w-full">
|
<label class="form-control w-full">
|
||||||
<div class="label"><span class="label-text font-bold">Alamat</span></div>
|
<div class="label"><span class="label-text font-bold">Alamat</span></div>
|
||||||
<textarea id="ib_alamat" class="textarea textarea-bordered h-20" placeholder="Alamat lengkap..."></textarea>
|
<textarea id="ib_alamat" class="textarea textarea-bordered h-16" placeholder="Alamat lengkap..."></textarea>
|
||||||
</label>
|
</label>
|
||||||
|
<div class="grid grid-cols-2 gap-3">
|
||||||
|
<label class="form-control w-full">
|
||||||
|
<div class="label"><span class="label-text font-bold">Pengurus</span></div>
|
||||||
|
<input type="text" id="ib_pengurus" placeholder="Nama ketua/pengurus" class="input input-bordered w-full" />
|
||||||
|
</label>
|
||||||
|
<label class="form-control w-full">
|
||||||
|
<div class="label"><span class="label-text font-bold">Telepon</span></div>
|
||||||
|
<input type="text" id="ib_telepon" placeholder="08..." class="input input-bordered w-full" />
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
<div class="grid grid-cols-2 gap-3">
|
||||||
|
<label class="form-control w-full">
|
||||||
|
<div class="label"><span class="label-text font-bold">Jumlah Jemaah</span></div>
|
||||||
|
<input type="number" id="ib_jemaah" value="100" class="input input-bordered w-full" />
|
||||||
|
</label>
|
||||||
|
<label class="form-control w-full">
|
||||||
|
<div class="label"><span class="label-text font-bold">Radius Analisis (m)</span></div>
|
||||||
|
<input type="number" id="ib_radius" value="500" class="input input-bordered w-full" />
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
<label class="form-control w-full">
|
<label class="form-control w-full">
|
||||||
<div class="label"><span class="label-text font-bold">Radius Default Analysis (Meter)</span></div>
|
<div class="label"><span class="label-text font-bold">Keterangan</span></div>
|
||||||
<input type="number" id="ib_radius" value="500" class="input input-bordered w-full" />
|
<textarea id="ib_ket" class="textarea textarea-bordered h-16" placeholder="Catatan tambahan..."></textarea>
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
<div class="modal-action">
|
<div class="modal-action">
|
||||||
@@ -205,13 +264,19 @@
|
|||||||
</h3>
|
</h3>
|
||||||
<div class="form-control gap-3">
|
<div class="form-control gap-3">
|
||||||
<div class="bg-base-200 p-2 rounded text-xs font-mono" id="rm_coords">Lat: -, Lng: -</div>
|
<div class="bg-base-200 p-2 rounded text-xs font-mono" id="rm_coords">Lat: -, Lng: -</div>
|
||||||
|
<div class="grid grid-cols-2 gap-3">
|
||||||
|
<label class="form-control w-full">
|
||||||
|
<div class="label"><span class="label-text font-bold">ID Rumah</span></div>
|
||||||
|
<input type="text" id="rm_id" placeholder="Contoh: RM-001" class="input input-bordered w-full" />
|
||||||
|
</label>
|
||||||
|
<label class="form-control w-full">
|
||||||
|
<div class="label"><span class="label-text font-bold">Nama Kepala Keluarga</span></div>
|
||||||
|
<input type="text" id="rm_nama_kk" placeholder="Nama lengkap KK" class="input input-bordered w-full" />
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
<label class="form-control w-full">
|
<label class="form-control w-full">
|
||||||
<div class="label"><span class="label-text font-bold">Alamat</span></div>
|
<div class="label"><span class="label-text font-bold">Alamat</span></div>
|
||||||
<textarea id="rm_alamat" class="textarea textarea-bordered h-20" placeholder="Alamat lengkap..."></textarea>
|
<textarea id="rm_alamat" class="textarea textarea-bordered h-16" placeholder="Alamat lengkap..."></textarea>
|
||||||
</label>
|
|
||||||
<label class="form-control w-full">
|
|
||||||
<div class="label"><span class="label-text font-bold">ID Rumah / Nama Pemilik</span></div>
|
|
||||||
<input type="text" id="rm_id" placeholder="Contoh: RM-001" class="input input-bordered w-full" />
|
|
||||||
</label>
|
</label>
|
||||||
<div class="grid grid-cols-2 gap-3">
|
<div class="grid grid-cols-2 gap-3">
|
||||||
<label class="form-control w-full">
|
<label class="form-control w-full">
|
||||||
@@ -223,6 +288,28 @@
|
|||||||
<input type="number" id="rm_jiwa" value="4" class="input input-bordered w-full" />
|
<input type="number" id="rm_jiwa" value="4" class="input input-bordered w-full" />
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="grid grid-cols-2 gap-3">
|
||||||
|
<label class="form-control w-full">
|
||||||
|
<div class="label"><span class="label-text font-bold">Pekerjaan</span></div>
|
||||||
|
<input type="text" id="rm_kerja" placeholder="Pekerjaan KK" class="input input-bordered w-full" />
|
||||||
|
</label>
|
||||||
|
<label class="form-control w-full">
|
||||||
|
<div class="label"><span class="label-text font-bold">Pendapatan (Rp)</span></div>
|
||||||
|
<input type="number" id="rm_duit" placeholder="Estimasi per bulan" class="input input-bordered w-full" />
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
<label class="form-control w-full">
|
||||||
|
<div class="label"><span class="label-text font-bold">Kondisi Rumah</span></div>
|
||||||
|
<select id="rm_kondisi" class="select select-bordered w-full">
|
||||||
|
<option value="sedang">Sedang</option>
|
||||||
|
<option value="buruk">Buruk</option>
|
||||||
|
<option value="sangat_buruk">Sangat Buruk</option>
|
||||||
|
</select>
|
||||||
|
</label>
|
||||||
|
<label class="form-control w-full">
|
||||||
|
<div class="label"><span class="label-text font-bold">Keterangan</span></div>
|
||||||
|
<textarea id="rm_ket" class="textarea textarea-bordered h-16" placeholder="Catatan tambahan..."></textarea>
|
||||||
|
</label>
|
||||||
</div>
|
</div>
|
||||||
<div class="modal-action">
|
<div class="modal-action">
|
||||||
<button class="btn btn-ghost" onclick="modalMiskin.close()">Batal</button>
|
<button class="btn btn-ghost" onclick="modalMiskin.close()">Batal</button>
|
||||||
@@ -249,6 +336,11 @@
|
|||||||
|
|
||||||
<!-- ═══ JAVASCRIPT ════════════════════════════════════════════════════════ -->
|
<!-- ═══ JAVASCRIPT ════════════════════════════════════════════════════════ -->
|
||||||
<script>
|
<script>
|
||||||
|
const authUserRole = '{{ auth()->user()->getRoleNames()->first() ?? '' }}';
|
||||||
|
const canEditIbadah = ['administrator', 'pengurus_ibadah'].includes(authUserRole);
|
||||||
|
const canEditMiskin = ['administrator', 'petugas_pendataan'].includes(authUserRole);
|
||||||
|
const csrfToken = document.querySelector('meta[name="csrf-token"]').getAttribute('content');
|
||||||
|
|
||||||
// ── CONFIG & STATE ──────────────────────────────────────────────────
|
// ── CONFIG & STATE ──────────────────────────────────────────────────
|
||||||
let map, currentMode = 'view';
|
let map, currentMode = 'view';
|
||||||
let layerIbadah = L.layerGroup(), layerMiskin = L.layerGroup();
|
let layerIbadah = L.layerGroup(), layerMiskin = L.layerGroup();
|
||||||
@@ -261,10 +353,13 @@
|
|||||||
|
|
||||||
const icons = {
|
const icons = {
|
||||||
ibadah: (color = '#22c55e') => L.divIcon({
|
ibadah: (color = '#22c55e') => L.divIcon({
|
||||||
html: `<div style="background:${color};width:32px;height:32px;border-radius:32px 32px 0 32px;border:3px solid white;transform:rotate(-45deg);display:flex;align-items:center;justify-center;box-shadow:0 4px 10px rgba(0,0,0,0.3);position:relative">
|
html: `<div style="display:flex;flex-direction:column;align-items:center;width:36px;height:50px;">
|
||||||
<i class="fa-solid fa-mosque" style="transform:rotate(45deg);color:white;font-size:14px;margin-left:6px;margin-top:2px"></i>
|
<div style="background:${color};width:36px;height:36px;border-radius:50%;border:3px solid white;box-shadow:0 4px 12px rgba(0,0,0,0.4);display:flex;align-items:center;justify-content:center;flex-shrink:0;">
|
||||||
|
<i class="fa-solid fa-mosque" style="color:white;font-size:15px;"></i>
|
||||||
|
</div>
|
||||||
|
<div style="width:0;height:0;border-left:8px solid transparent;border-right:8px solid transparent;border-top:14px solid ${color};margin-top:-1px;flex-shrink:0;"></div>
|
||||||
</div>`,
|
</div>`,
|
||||||
className: '', iconSize: [32, 32], iconAnchor: [0, 32], popupAnchor: [16, -35]
|
className: '', iconSize: [36, 50], iconAnchor: [18, 50], popupAnchor: [0, -52]
|
||||||
}),
|
}),
|
||||||
miskin: (color = '#ef4444', glow = false) => L.divIcon({
|
miskin: (color = '#ef4444', glow = false) => L.divIcon({
|
||||||
html: `<div style="background:${color};width:24px;height:24px;border-radius:24px;border:2px solid white;display:flex;align-items:center;justify-content:center;box-shadow:0 3px 8px rgba(0,0,0,0.3);${glow ? 'box-shadow: 0 0 15px ' + color : ''}">
|
html: `<div style="background:${color};width:24px;height:24px;border-radius:24px;border:2px solid white;display:flex;align-items:center;justify-content:center;box-shadow:0 3px 8px rgba(0,0,0,0.3);${glow ? 'box-shadow: 0 0 15px ' + color : ''}">
|
||||||
@@ -282,6 +377,14 @@
|
|||||||
document.addEventListener('DOMContentLoaded', () => {
|
document.addEventListener('DOMContentLoaded', () => {
|
||||||
initMap();
|
initMap();
|
||||||
loadData();
|
loadData();
|
||||||
|
|
||||||
|
// Hide add buttons for roles that can't edit
|
||||||
|
if (!canEditIbadah) {
|
||||||
|
document.querySelectorAll('[data-mode="ibadah"], #btnModeIbadah, .btn-add-ibadah').forEach(el => el.classList.add('hidden'));
|
||||||
|
}
|
||||||
|
if (!canEditMiskin) {
|
||||||
|
document.querySelectorAll('[data-mode="miskin"], #btnModeMiskin, .btn-add-miskin').forEach(el => el.classList.add('hidden'));
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
function initMap() {
|
function initMap() {
|
||||||
@@ -505,7 +608,7 @@
|
|||||||
try {
|
try {
|
||||||
const res = await fetch(`{{ url('/api/ibadah') }}/${activeIbadah.id}`, {
|
const res = await fetch(`{{ url('/api/ibadah') }}/${activeIbadah.id}`, {
|
||||||
method: 'PUT',
|
method: 'PUT',
|
||||||
headers: { 'Content-Type': 'application/json' },
|
headers: { 'Content-Type': 'application/json', 'X-CSRF-TOKEN': csrfToken },
|
||||||
body: JSON.stringify({
|
body: JSON.stringify({
|
||||||
id: activeIbadah.id,
|
id: activeIbadah.id,
|
||||||
nama: activeIbadah.nama,
|
nama: activeIbadah.nama,
|
||||||
@@ -530,22 +633,31 @@
|
|||||||
|
|
||||||
// ── POPUPS ──────────────────────────────────────────────────────────
|
// ── POPUPS ──────────────────────────────────────────────────────────
|
||||||
function buildPopupIbadah(item) {
|
function buildPopupIbadah(item) {
|
||||||
return `<div class="p-1">
|
return `<div class="p-2 min-w-[200px]">
|
||||||
<div class="font-bold text-sm">🕌 ${item.nama}</div>
|
<div class="font-bold text-base text-primary">🕌 ${item.nama}</div>
|
||||||
<div class="text-[10px] text-gray-500 mb-2">${item.alamat}</div>
|
<div class="badge badge-sm badge-outline mb-2 capitalize">${item.jenis || 'Ibadah'}</div>
|
||||||
|
<div class="text-[10px] text-gray-500 mb-1"><i class="fa-solid fa-location-dot"></i> ${item.alamat}</div>
|
||||||
|
<div class="grid grid-cols-2 gap-2 my-2 text-[10px] bg-base-200 p-2 rounded">
|
||||||
|
<div><strong>Pengurus:</strong><br>${item.pengurus || '-'}</div>
|
||||||
|
<div><strong>Jemaah:</strong><br>${item.jumlah_jemaah || 0}</div>
|
||||||
|
</div>
|
||||||
<div class="flex gap-1 border-t pt-2 mt-1">
|
<div class="flex gap-1 border-t pt-2 mt-1">
|
||||||
<button class="btn btn-xs btn-outline btn-error" onclick="prepDelete('ibadah', ${item.id})">Hapus</button>
|
${canEditIbadah ? `<button class="btn btn-xs btn-error btn-outline" onclick="prepDelete('ibadah', ${item.id})">Hapus</button>` : ''}
|
||||||
</div>
|
</div>
|
||||||
</div>`;
|
</div>`;
|
||||||
}
|
}
|
||||||
|
|
||||||
function buildPopupMiskin(item) {
|
function buildPopupMiskin(item) {
|
||||||
return `<div class="p-1">
|
return `<div class="p-2 min-w-[200px]">
|
||||||
<div class="font-bold text-sm">🏠 ID: ${item.id_rumah}</div>
|
<div class="font-bold text-base text-error">🏠 ${item.nama_kk || 'Keluarga Miskin'}</div>
|
||||||
<div class="text-[10px] text-gray-500 mb-1">${item.alamat || 'Alamat tidak tersedia'}</div>
|
<div class="text-[10px] text-gray-400 mb-1">ID: ${item.id_rumah}</div>
|
||||||
<div class="text-[10px] text-gray-400">KK: ${item.jumlah_kk} | Jiwa: ${item.jumlah_orang}</div>
|
<div class="text-[10px] text-gray-500 mb-1"><i class="fa-solid fa-location-dot"></i> ${item.alamat || '-'}</div>
|
||||||
|
<div class="grid grid-cols-2 gap-2 my-2 text-[10px] bg-base-200 p-2 rounded">
|
||||||
|
<div><strong>KK/Jiwa:</strong><br>${item.jumlah_kk}/${item.jumlah_orang}</div>
|
||||||
|
<div><strong>Kondisi:</strong><br><span class="capitalize">${item.kondisi_rumah || '-'}</span></div>
|
||||||
|
</div>
|
||||||
<div class="flex gap-1 border-t pt-2 mt-1">
|
<div class="flex gap-1 border-t pt-2 mt-1">
|
||||||
<button class="btn btn-xs btn-outline btn-error" onclick="prepDelete('miskin', '${item.id_rumah}')">Hapus</button>
|
${canEditMiskin ? `<button class="btn btn-xs btn-error btn-outline" onclick="prepDelete('miskin', '${item.id_rumah}')">Hapus</button>` : ''}
|
||||||
</div>
|
</div>
|
||||||
</div>`;
|
</div>`;
|
||||||
}
|
}
|
||||||
@@ -555,17 +667,22 @@
|
|||||||
const pos = tempMarker.getLatLng();
|
const pos = tempMarker.getLatLng();
|
||||||
const data = {
|
const data = {
|
||||||
nama: document.getElementById('ib_nama').value,
|
nama: document.getElementById('ib_nama').value,
|
||||||
|
jenis: document.getElementById('ib_jenis').value,
|
||||||
alamat: document.getElementById('ib_alamat').value,
|
alamat: document.getElementById('ib_alamat').value,
|
||||||
latitude: pos.lat, longitude: pos.lng,
|
latitude: pos.lat, longitude: pos.lng,
|
||||||
radius: document.getElementById('ib_radius').value
|
radius: document.getElementById('ib_radius').value,
|
||||||
|
jumlah_jemaah: document.getElementById('ib_jemaah').value,
|
||||||
|
pengurus: document.getElementById('ib_pengurus').value,
|
||||||
|
telepon: document.getElementById('ib_telepon').value,
|
||||||
|
keterangan: document.getElementById('ib_ket').value
|
||||||
};
|
};
|
||||||
const btn = document.getElementById('btnSaveIbadah');
|
const btn = document.getElementById('btnSaveIbadah');
|
||||||
btn.disabled = true;
|
btn.disabled = true;
|
||||||
|
|
||||||
const res = await fetch('{{ url('/api/ibadah') }}', {
|
const res = await fetch('{{ url('/api/ibadah') }}', {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: { 'Content-Type': 'application/json' },
|
headers: { 'Content-Type': 'application/json', 'X-CSRF-TOKEN': csrfToken },
|
||||||
body: JSON.stringify(data)
|
body: JSON.stringify(data)
|
||||||
});
|
});
|
||||||
const resJson = await res.json();
|
const resJson = await res.json();
|
||||||
if (resJson.success) {
|
if (resJson.success) {
|
||||||
@@ -580,18 +697,23 @@
|
|||||||
const pos = tempMarker.getLatLng();
|
const pos = tempMarker.getLatLng();
|
||||||
const data = {
|
const data = {
|
||||||
id_rumah: document.getElementById('rm_id').value,
|
id_rumah: document.getElementById('rm_id').value,
|
||||||
|
nama_kk: document.getElementById('rm_nama_kk').value,
|
||||||
alamat: document.getElementById('rm_alamat').value,
|
alamat: document.getElementById('rm_alamat').value,
|
||||||
jumlah_kk: document.getElementById('rm_kk').value,
|
jumlah_kk: document.getElementById('rm_kk').value,
|
||||||
jumlah_orang: document.getElementById('rm_jiwa').value,
|
jumlah_orang: document.getElementById('rm_jiwa').value,
|
||||||
latitude: pos.lat, longitude: pos.lng
|
latitude: pos.lat, longitude: pos.lng,
|
||||||
|
pekerjaan: document.getElementById('rm_kerja').value,
|
||||||
|
pendapatan: document.getElementById('rm_duit').value,
|
||||||
|
kondisi_rumah: document.getElementById('rm_kondisi').value,
|
||||||
|
keterangan: document.getElementById('rm_ket').value
|
||||||
};
|
};
|
||||||
const btn = document.getElementById('btnSaveMiskin');
|
const btn = document.getElementById('btnSaveMiskin');
|
||||||
btn.disabled = true;
|
btn.disabled = true;
|
||||||
|
|
||||||
const res = await fetch('{{ url('/api/miskin') }}', {
|
const res = await fetch('{{ url('/api/miskin') }}', {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: { 'Content-Type': 'application/json' },
|
headers: { 'Content-Type': 'application/json', 'X-CSRF-TOKEN': csrfToken },
|
||||||
body: JSON.stringify(data)
|
body: JSON.stringify(data)
|
||||||
});
|
});
|
||||||
const resJson = await res.json();
|
const resJson = await res.json();
|
||||||
if (resJson.success) {
|
if (resJson.success) {
|
||||||
@@ -609,10 +731,10 @@
|
|||||||
: { id_rumah: id, alamat: oldData.alamat, jumlah_kk: oldData.jumlah_kk, jumlah_orang: oldData.jumlah_orang, latitude: latlng.lat, longitude: latlng.lng };
|
: { id_rumah: id, alamat: oldData.alamat, jumlah_kk: oldData.jumlah_kk, jumlah_orang: oldData.jumlah_orang, latitude: latlng.lat, longitude: latlng.lng };
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const res = await fetch(endpoint, {
|
const res = await fetch(endpoint, {
|
||||||
method: 'PUT',
|
method: 'PUT',
|
||||||
headers: { 'Content-Type': 'application/json' },
|
headers: { 'Content-Type': 'application/json', 'X-CSRF-TOKEN': csrfToken },
|
||||||
body: JSON.stringify(payload)
|
body: JSON.stringify(payload)
|
||||||
});
|
});
|
||||||
const resJson = await res.json();
|
const resJson = await res.json();
|
||||||
if (resJson.success) {
|
if (resJson.success) {
|
||||||
@@ -637,9 +759,9 @@
|
|||||||
|
|
||||||
document.getElementById('confirmDeleteBtn').onclick = async () => {
|
document.getElementById('confirmDeleteBtn').onclick = async () => {
|
||||||
const { type, id } = deleteTarget;
|
const { type, id } = deleteTarget;
|
||||||
const res = await fetch(`{{ url('/api') }}/${type}/${id}`, {
|
const res = await fetch(`{{ url('/api') }}/${type}/${id}`, {
|
||||||
method: 'DELETE',
|
method: 'DELETE',
|
||||||
headers: { 'Content-Type': 'application/json' }
|
headers: { 'Content-Type': 'application/json', 'X-CSRF-TOKEN': csrfToken }
|
||||||
});
|
});
|
||||||
const resJson = await res.json();
|
const resJson = await res.json();
|
||||||
if (resJson.success) {
|
if (resJson.success) {
|
||||||
|
|||||||
@@ -3,7 +3,8 @@
|
|||||||
<head>
|
<head>
|
||||||
<meta charset="utf-8">
|
<meta charset="utf-8">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
<title>WebGIS Miskin & Ibadah</title>
|
<meta name="csrf-token" content="{{ csrf_token() }}">
|
||||||
|
<title>WebGIS Pemetaan Kemiskinan</title>
|
||||||
|
|
||||||
<!-- Leaflet -->
|
<!-- Leaflet -->
|
||||||
<link rel="stylesheet" href="https://unpkg.com/leaflet@1.9.4/dist/leaflet.css">
|
<link rel="stylesheet" href="https://unpkg.com/leaflet@1.9.4/dist/leaflet.css">
|
||||||
@@ -68,7 +69,7 @@
|
|||||||
<div class="hero-content text-center flex-col gap-2">
|
<div class="hero-content text-center flex-col gap-2">
|
||||||
<!-- Logo -->
|
<!-- Logo -->
|
||||||
<div class="text-7xl mb-2">🏘️</div>
|
<div class="text-7xl mb-2">🏘️</div>
|
||||||
<h1 class="text-4xl font-bold tracking-tight text-primary">WebGIS Miskin & Ibadah</h1>
|
<h1 class="text-4xl font-bold tracking-tight text-primary">WebGIS Pemetaan Kemiskinan</h1>
|
||||||
<p class="max-w-md text-base-content/60 mt-2 mb-6 leading-relaxed">
|
<p class="max-w-md text-base-content/60 mt-2 mb-6 leading-relaxed">
|
||||||
Sistem Informasi Geografis untuk pemetaan rumah ibadah dan
|
Sistem Informasi Geografis untuk pemetaan rumah ibadah dan
|
||||||
analisis sebaran keluarga kurang mampu.
|
analisis sebaran keluarga kurang mampu.
|
||||||
@@ -86,6 +87,19 @@
|
|||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- User info -->
|
||||||
|
<div class="flex items-center gap-2 text-sm text-base-content/50 mt-2">
|
||||||
|
<i class="fa-solid fa-user text-xs"></i>
|
||||||
|
<span>{{ auth()->user()->name }}</span>
|
||||||
|
<span class="opacity-30">•</span>
|
||||||
|
<span class="capitalize">{{ str_replace('_', ' ', auth()->user()->getRoleNames()->first() ?? '') }}</span>
|
||||||
|
<span class="opacity-30">•</span>
|
||||||
|
<form method="POST" action="{{ route('logout') }}" class="inline">
|
||||||
|
@csrf
|
||||||
|
<button type="submit" class="hover:text-error transition-colors underline underline-offset-2">Keluar</button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
|
||||||
<!-- Redirect progress -->
|
<!-- Redirect progress -->
|
||||||
<div class="mt-8 w-72">
|
<div class="mt-8 w-72">
|
||||||
<p class="text-xs text-base-content/40 mb-2">Mengarahkan ke halaman Peta dalam 5 detik...</p>
|
<p class="text-xs text-base-content/40 mb-2">Mengarahkan ke halaman Peta dalam 5 detik...</p>
|
||||||
|
|||||||
@@ -105,4 +105,27 @@ class AuthTest extends TestCase
|
|||||||
$response = $this->actingAs($viewer)->delete('/api/ibadah/999');
|
$response = $this->actingAs($viewer)->delete('/api/ibadah/999');
|
||||||
$response->assertStatus(403);
|
$response->assertStatus(403);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function test_data_page_shows_user_name_and_logout(): void
|
||||||
|
{
|
||||||
|
$this->artisan('db:seed', ['--class' => 'RolesAndAdminSeeder']);
|
||||||
|
$user = User::where('email', 'admin@webgis.local')->first();
|
||||||
|
|
||||||
|
$response = $this->actingAs($user)->get('/data');
|
||||||
|
|
||||||
|
$response->assertStatus(200);
|
||||||
|
$response->assertSee('Keluar');
|
||||||
|
$response->assertSee($user->name);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function test_map_page_passes_user_role_to_js(): void
|
||||||
|
{
|
||||||
|
$this->artisan('db:seed', ['--class' => 'RolesAndAdminSeeder']);
|
||||||
|
$user = User::where('email', 'admin@webgis.local')->first();
|
||||||
|
|
||||||
|
$response = $this->actingAs($user)->get('/map');
|
||||||
|
|
||||||
|
$response->assertStatus(200);
|
||||||
|
$response->assertSee("authUserRole = 'administrator'", false);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user