Initial commit WebGIS
This commit is contained in:
@@ -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>
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user