Initial commit WebGIS

This commit is contained in:
2026-06-09 19:45:18 +07:00
commit 2d8917af26
27 changed files with 5807 additions and 0 deletions
+446
View File
@@ -0,0 +1,446 @@
import { useMemo, useState } from 'react'
import Sidebar from '../components/Sidebar.jsx'
import Topbar from '../components/Topbar.jsx'
import {
loadWarga,
saveWarga
} from '../lib/storage.js'
export default function Verifikasi() {
const [rows, setRows] =
useState(() => loadWarga())
const [search, setSearch] =
useState('')
const [filterStatus, setFilterStatus] =
useState('Semua')
const [editId, setEditId] =
useState(null)
const [editForm, setEditForm] =
useState({
nama: '',
wilayah: '',
penghasilan: ''
})
function updateStatus(id, status) {
const updated = rows.map(r => {
if (r.id === id) {
return {
...r,
statusValidasi: status
}
}
return r
})
setRows(updated)
saveWarga(updated)
}
function mulaiEdit(row) {
setEditId(row.id)
setEditForm({
nama: row.nama,
wilayah: row.wilayah,
penghasilan: row.penghasilan
})
}
function simpanEdit(id) {
const updated = rows.map(r => {
if (r.id === id) {
return {
...r,
nama: editForm.nama,
wilayah: editForm.wilayah,
penghasilan:
editForm.penghasilan
}
}
return r
})
setRows(updated)
saveWarga(updated)
setEditId(null)
}
function hapusData(id) {
const confirmDelete =
confirm(
'Yakin ingin menghapus data?'
)
if (!confirmDelete) return
const filtered =
rows.filter(r => r.id !== id)
setRows(filtered)
saveWarga(filtered)
}
const filteredRows = useMemo(() => {
return rows.filter(r => {
const cocokNama =
r.nama
.toLowerCase()
.includes(
search.toLowerCase()
)
const cocokStatus =
filterStatus === 'Semua'
? true
: r.statusValidasi ===
filterStatus
return cocokNama &&
cocokStatus
})
}, [
rows,
search,
filterStatus
])
return (
<div className="flex min-h-screen bg-slate-100">
<Sidebar />
<div className="flex-1">
<Topbar />
<main className="p-6">
<div className="bg-white rounded-3xl shadow p-6">
<div className="flex items-center justify-between mb-6">
<div>
<h1 className="text-3xl font-extrabold">
Verifikasi Data
</h1>
<p className="text-slate-500 mt-1">
Approve atau reject data warga
</p>
</div>
</div>
<div className="flex gap-4 mb-6">
<input
placeholder="Cari nama warga..."
value={search}
onChange={e =>
setSearch(
e.target.value
)
}
className="border rounded-2xl px-4 py-3 w-full"
/>
<select
value={filterStatus}
onChange={e =>
setFilterStatus(
e.target.value
)
}
className="border rounded-2xl px-4 py-3"
>
<option>
Semua
</option>
<option>
Pending
</option>
<option>
Verified
</option>
<option>
Rejected
</option>
</select>
</div>
<div className="overflow-auto">
<table className="w-full">
<thead>
<tr className="border-b">
<th className="text-left py-4">
ID
</th>
<th className="text-left py-4">
Nama
</th>
<th className="text-left py-4">
Wilayah
</th>
<th className="text-left py-4">
Penghasilan
</th>
<th className="text-left py-4">
Status
</th>
<th className="text-left py-4">
Aksi
</th>
</tr>
</thead>
<tbody>
{filteredRows.map(r => (
<tr
key={r.id}
className="border-b hover:bg-slate-50"
>
<td className="py-4">
{r.id}
</td>
<td className="py-4">
{editId === r.id ? (
<input
value={editForm.nama}
onChange={e =>
setEditForm({
...editForm,
nama:
e.target.value
})
}
className="border rounded-xl px-3 py-2"
/>
) : (
r.nama
)}
</td>
<td className="py-4">
{editId === r.id ? (
<input
value={editForm.wilayah}
onChange={e =>
setEditForm({
...editForm,
wilayah:
e.target.value
})
}
className="border rounded-xl px-3 py-2"
/>
) : (
r.wilayah
)}
</td>
<td className="py-4">
{editId === r.id ? (
<input
value={
editForm.penghasilan
}
onChange={e =>
setEditForm({
...editForm,
penghasilan:
e.target.value
})
}
className="border rounded-xl px-3 py-2"
/>
) : (
<>Rp {r.penghasilan}</>
)}
</td>
<td className="py-4">
{r.statusValidasi ===
'Pending' && (
<span className="bg-yellow-100 text-yellow-700 px-3 py-1 rounded-full text-sm">
Pending
</span>
)}
{r.statusValidasi ===
'Verified' && (
<span className="bg-green-100 text-green-700 px-3 py-1 rounded-full text-sm">
Verified
</span>
)}
{r.statusValidasi ===
'Rejected' && (
<span className="bg-red-100 text-red-700 px-3 py-1 rounded-full text-sm">
Rejected
</span>
)}
</td>
<td className="py-4">
<div className="flex gap-2 flex-wrap">
{r.statusValidasi ===
'Pending' && (
<>
<button
onClick={() =>
updateStatus(
r.id,
'Verified'
)
}
className="bg-green-500 hover:bg-green-600 text-white px-4 py-2 rounded-xl"
>
Approve
</button>
<button
onClick={() =>
updateStatus(
r.id,
'Rejected'
)
}
className="bg-red-500 hover:bg-red-600 text-white px-4 py-2 rounded-xl"
>
Reject
</button>
</>
)}
{editId === r.id ? (
<button
onClick={() =>
simpanEdit(r.id)
}
className="bg-blue-500 hover:bg-blue-600 text-white px-4 py-2 rounded-xl"
>
Simpan
</button>
) : (
<button
onClick={() =>
mulaiEdit(r)
}
className="bg-slate-800 hover:bg-slate-900 text-white px-4 py-2 rounded-xl"
>
Edit
</button>
)}
<button
onClick={() =>
hapusData(r.id)
}
className="bg-red-500 hover:bg-red-600 text-white px-4 py-2 rounded-xl"
>
Hapus
</button>
</div>
</td>
</tr>
))}
</tbody>
</table>
</div>
</div>
</main>
</div>
</div>
)
}