first commit

This commit is contained in:
akbarguci.pkl 2026-05-19 08:56:45 +07:00
parent e8f3995cea
commit 03229101d5
3 changed files with 251 additions and 42 deletions

10
package-lock.json generated
View File

@ -14,6 +14,7 @@
"lucide-react": "^1.14.0", "lucide-react": "^1.14.0",
"react": "^19.2.5", "react": "^19.2.5",
"react-dom": "^19.2.5", "react-dom": "^19.2.5",
"react-icons": "^5.6.0",
"react-router-dom": "^7.14.2" "react-router-dom": "^7.14.2"
}, },
"devDependencies": { "devDependencies": {
@ -3521,6 +3522,15 @@
"react": "^19.2.5" "react": "^19.2.5"
} }
}, },
"node_modules/react-icons": {
"version": "5.6.0",
"resolved": "https://registry.npmjs.org/react-icons/-/react-icons-5.6.0.tgz",
"integrity": "sha512-RH93p5ki6LfOiIt0UtDyNg/cee+HLVR6cHHtW3wALfo+eOHTp8RnU2kRkI6E+H19zMIs03DyxUG/GfZMOGvmiA==",
"license": "MIT",
"peerDependencies": {
"react": "*"
}
},
"node_modules/react-router": { "node_modules/react-router": {
"version": "7.14.2", "version": "7.14.2",
"resolved": "https://registry.npmjs.org/react-router/-/react-router-7.14.2.tgz", "resolved": "https://registry.npmjs.org/react-router/-/react-router-7.14.2.tgz",

View File

@ -16,6 +16,7 @@
"lucide-react": "^1.14.0", "lucide-react": "^1.14.0",
"react": "^19.2.5", "react": "^19.2.5",
"react-dom": "^19.2.5", "react-dom": "^19.2.5",
"react-icons": "^5.6.0",
"react-router-dom": "^7.14.2" "react-router-dom": "^7.14.2"
}, },
"devDependencies": { "devDependencies": {

View File

@ -2,15 +2,25 @@ import { useState } from "react";
import axios from "axios"; import axios from "axios";
import type { UserData } from "../../routes/ManajementUsers"; import type { UserData } from "../../routes/ManajementUsers";
import { FaEdit } from "react-icons/fa";
import { MdDelete } from "react-icons/md";
interface TableProps { interface TableProps {
dataUsers: UserData[]; dataUsers: UserData[];
onHapusUser: (user_id: number) => void; onHapusUser: (user_id: number) => void;
onUserDiedit: () => void; onUserDiedit: () => void;
} }
export default function UsersTable({ dataUsers, onHapusUser, onUserDiedit }: TableProps) { export default function UsersTable({
dataUsers,
onHapusUser,
onUserDiedit,
}: TableProps) {
const [isEditOpen, setIsEditOpen] = useState(false); const [isEditOpen, setIsEditOpen] = useState(false);
const [targetId, setTargetId] = useState<number | null>(null); const [targetId, setTargetId] = useState<number | null>(null);
const [editFormData, setEditFormData] = useState({ const [editFormData, setEditFormData] = useState({
nama_lengkap: "", nama_lengkap: "",
username: "", username: "",
@ -18,11 +28,14 @@ export default function UsersTable({ dataUsers, onHapusUser, onUserDiedit }: Tab
email: "", email: "",
lembaga: "", lembaga: "",
}); });
const [loading, setLoading] = useState(false); const [loading, setLoading] = useState(false);
// Buka Modal Edit dan set data default-nya ke form // BUKA MODAL EDIT
const bukaModalEdit = (user: UserData) => { const bukaModalEdit = (user: UserData) => {
setTargetId(user.user_id); setTargetId(user.user_id);
setEditFormData({ setEditFormData({
nama_lengkap: user.nama_lengkap, nama_lengkap: user.nama_lengkap,
username: user.username, username: user.username,
@ -30,60 +43,133 @@ export default function UsersTable({ dataUsers, onHapusUser, onUserDiedit }: Tab
email: user.email || "", email: user.email || "",
lembaga: user.lembaga || "", lembaga: user.lembaga || "",
}); });
setIsEditOpen(true); setIsEditOpen(true);
}; };
const handleUpdate = async (e: React.FormEvent) => { // UPDATE USER
const handleUpdate = async (
e: React.FormEvent
) => {
e.preventDefault(); e.preventDefault();
if (!targetId) return; if (!targetId) return;
setLoading(true); setLoading(true);
try { try {
const respon = await axios.put(`http://localhost:5000/api/users/${targetId}`, editFormData);
const respon = await axios.put(
`http://localhost:5000/api/users/${targetId}`,
editFormData
);
if (respon.data.success) { if (respon.data.success) {
alert(respon.data.message); alert(respon.data.message);
setIsEditOpen(false); setIsEditOpen(false);
onUserDiedit(); // Memicu refresh state di rute induk
onUserDiedit();
} }
} catch (error: any) { } catch (error: any) {
console.error(error); console.error(error);
alert(error.response?.data?.message || "Gagal memperbarui data user!");
alert(
error.response?.data?.message ||
"Gagal memperbarui data user!"
);
} finally { } finally {
setLoading(false); setLoading(false);
} }
}; };
return ( return (
<div className="bg-white rounded-3xl shadow-sm border border-blue-100 overflow-hidden"> <div className="bg-white rounded-3xl shadow-sm border border-blue-100 overflow-hidden">
<div className="overflow-x-auto"> <div className="overflow-x-auto">
<table className="w-full text-left border-collapse"> <table className="w-full text-left border-collapse">
<thead> <thead>
<tr className="bg-blue-50 border-b border-blue-100 text-blue-800 font-bold text-sm"> <tr className="bg-blue-50 border-b border-blue-100 text-blue-800 font-bold text-sm">
<th className="p-4 pl-6">ID Pengguna</th>
<th className="p-4">Nama Lengkap</th> <th className="p-4 pl-6">
<th className="p-4">Username</th> ID Pengguna
<th className="p-4">Hak Akses / Role</th> </th>
<th className="p-4">Email</th>
<th className="p-4">Lembaga / Instansi</th> <th className="p-4">
<th className="p-4 text-center pr-6">Aksi Operasional</th> Nama Lengkap
</th>
<th className="p-4">
Username
</th>
<th className="p-4">
Hak Akses / Role
</th>
<th className="p-4">
Email
</th>
<th className="p-4">
Lembaga / Instansi
</th>
<th className="p-4 text-center pr-6">
Aksi Operasional
</th>
</tr> </tr>
</thead> </thead>
<tbody className="divide-y divide-gray-100 text-sm text-gray-700"> <tbody className="divide-y divide-gray-100 text-sm text-gray-700">
{dataUsers.length === 0 ? ( {dataUsers.length === 0 ? (
<tr> <tr>
<td colSpan={7} className="p-8 text-center text-gray-400 font-medium">
<td
colSpan={7}
className="p-8 text-center text-gray-400 font-medium"
>
Belum ada data pengguna terdaftar atau kata kunci salah, Bang! 📭 Belum ada data pengguna terdaftar atau kata kunci salah, Bang! 📭
</td> </td>
</tr> </tr>
) : ( ) : (
dataUsers.map((user) => ( dataUsers.map((user) => (
<tr key={user.user_id} className="hover:bg-blue-50/40 transition">
<td className="p-4 pl-6 font-semibold text-gray-500">#{user.user_id}</td> <tr
<td className="p-4 font-bold text-gray-900">{user.nama_lengkap}</td> key={user.user_id}
className="hover:bg-blue-50/40 transition"
>
<td className="p-4 pl-6 font-semibold text-gray-500">
#{user.user_id}
</td>
<td className="p-4 font-bold text-gray-900">
{user.nama_lengkap}
</td>
<td className="p-4 font-mono text-gray-600 bg-gray-50/50 rounded-lg px-2 py-0.5 inline-block mt-3"> <td className="p-4 font-mono text-gray-600 bg-gray-50/50 rounded-lg px-2 py-0.5 inline-block mt-3">
{user.username} {user.username}
</td> </td>
<td className="p-4"> <td className="p-4">
<span <span
className={`px-3 py-1 rounded-full text-xs font-bold capitalize ${ className={`px-3 py-1 rounded-full text-xs font-bold capitalize ${
user.role === "Admin" user.role === "Admin"
@ -95,117 +181,229 @@ export default function UsersTable({ dataUsers, onHapusUser, onUserDiedit }: Tab
> >
{user.role} {user.role}
</span> </span>
</td> </td>
<td className="p-4 text-gray-500">{user.email || "-"}</td>
<td className="p-4 text-gray-600 font-medium">{user.lembaga || "-"}</td> <td className="p-4 text-gray-500">
{user.email || "-"}
</td>
<td className="p-4 text-gray-600 font-medium">
{user.lembaga || "-"}
</td>
<td className="p-4 pr-6 text-center"> <td className="p-4 pr-6 text-center">
<div className="flex justify-center items-center gap-2"> <div className="flex justify-center items-center gap-2">
{/* BUTTON EDIT */}
<button <button
onClick={() => bukaModalEdit(user)} onClick={() => bukaModalEdit(user)}
className="bg-amber-50 hover:bg-amber-100 text-amber-700 border border-amber-200 px-3 py-1.5 rounded-xl font-semibold transition" className="flex items-center gap-2 bg-amber-50 hover:bg-amber-100 text-amber-700 border border-amber-200 px-3 py-1.5 rounded-xl font-semibold transition"
> >
✏️ Edit
<FaEdit size={14} />
Edit
</button> </button>
{/* BUTTON HAPUS */}
<button <button
onClick={() => onHapusUser(user.user_id)} onClick={() =>
className="bg-red-50 hover:bg-red-100 text-red-600 border border-red-200 px-3 py-1.5 rounded-xl font-semibold transition" onHapusUser(user.user_id)
}
className="flex items-center gap-2 bg-red-50 hover:bg-red-100 text-red-600 border border-red-200 px-3 py-1.5 rounded-xl font-semibold transition"
> >
🗑️ Hapus
<MdDelete size={16} />
Hapus
</button> </button>
</div>
</td>
</tr>
))
)}
</tbody>
</table>
</div> </div>
{/* MODAL POP UP FORM EDIT USER */} </td>
</tr>
))
)}
</tbody>
</table>
</div>
{/* MODAL EDIT */}
{isEditOpen && ( {isEditOpen && (
<div className="fixed inset-0 bg-black/50 backdrop-blur-sm flex justify-center items-center z-50 p-4"> <div className="fixed inset-0 bg-black/50 backdrop-blur-sm flex justify-center items-center z-50 p-4">
<div className="bg-white rounded-3xl p-8 max-w-md w-full shadow-2xl border border-gray-100 text-gray-800 animate-in fade-in zoom-in-95 duration-200"> <div className="bg-white rounded-3xl p-8 max-w-md w-full shadow-2xl border border-gray-100 text-gray-800 animate-in fade-in zoom-in-95 duration-200">
<h2 className="text-2xl font-bold text-gray-900 mb-6 flex items-center gap-2 text-amber-600"> <h2 className="text-2xl font-bold text-gray-900 mb-6 flex items-center gap-2 text-amber-600">
✏️ Sunting Data Pengguna ✏️ Sunting Data Pengguna
</h2> </h2>
<form onSubmit={handleUpdate} className="space-y-4"> <form
onSubmit={handleUpdate}
className="space-y-4"
>
<div> <div>
<label className="block text-sm font-semibold text-gray-700 mb-1">Nama Lengkap *</label>
<label className="block text-sm font-semibold text-gray-700 mb-1">
Nama Lengkap *
</label>
<input <input
type="text" type="text"
required required
value={editFormData.nama_lengkap} value={editFormData.nama_lengkap}
onChange={(e) => setEditFormData({ ...editFormData, nama_lengkap: e.target.value })} onChange={(e) =>
setEditFormData({
...editFormData,
nama_lengkap: e.target.value,
})
}
className="w-full px-4 py-2.5 rounded-xl border border-gray-300 focus:ring-2 focus:ring-amber-500 focus:border-amber-500 outline-none transition" className="w-full px-4 py-2.5 rounded-xl border border-gray-300 focus:ring-2 focus:ring-amber-500 focus:border-amber-500 outline-none transition"
/> />
</div> </div>
<div> <div>
<label className="block text-sm font-semibold text-gray-700 mb-1">Username *</label>
<label className="block text-sm font-semibold text-gray-700 mb-1">
Username *
</label>
<input <input
type="text" type="text"
required required
value={editFormData.username} value={editFormData.username}
onChange={(e) => setEditFormData({ ...editFormData, username: e.target.value })} onChange={(e) =>
setEditFormData({
...editFormData,
username: e.target.value,
})
}
className="w-full px-4 py-2.5 rounded-xl border border-gray-300 focus:ring-2 focus:ring-amber-500 focus:border-amber-500 outline-none transition bg-gray-50" className="w-full px-4 py-2.5 rounded-xl border border-gray-300 focus:ring-2 focus:ring-amber-500 focus:border-amber-500 outline-none transition bg-gray-50"
/> />
</div> </div>
<div> <div>
<label className="block text-sm font-semibold text-gray-700 mb-1">Role / Hak Akses</label>
<label className="block text-sm font-semibold text-gray-700 mb-1">
Role / Hak Akses
</label>
<select <select
value={editFormData.role} value={editFormData.role}
onChange={(e) => setEditFormData({ ...editFormData, role: e.target.value })} onChange={(e) =>
setEditFormData({
...editFormData,
role: e.target.value,
})
}
className="w-full px-4 py-2.5 rounded-xl border border-gray-300 focus:ring-2 focus:ring-amber-500 focus:border-amber-500 outline-none transition bg-white" className="w-full px-4 py-2.5 rounded-xl border border-gray-300 focus:ring-2 focus:ring-amber-500 focus:border-amber-500 outline-none transition bg-white"
> >
<option value="Admin">Admin</option>
<option value="Petugas">Petugas</option> <option value="Admin">
<option value="Siswa">Siswa</option> Admin
<option value="User">User Biasa</option> </option>
<option value="Petugas">
Petugas
</option>
<option value="Siswa">
Siswa
</option>
<option value="User">
User Biasa
</option>
</select> </select>
</div> </div>
<div> <div>
<label className="block text-sm font-semibold text-gray-700 mb-1">Email</label>
<label className="block text-sm font-semibold text-gray-700 mb-1">
Email
</label>
<input <input
type="email" type="email"
value={editFormData.email} value={editFormData.email}
onChange={(e) => setEditFormData({ ...editFormData, email: e.target.value })} onChange={(e) =>
setEditFormData({
...editFormData,
email: e.target.value,
})
}
className="w-full px-4 py-2.5 rounded-xl border border-gray-300 focus:ring-2 focus:ring-amber-500 focus:border-amber-500 outline-none transition" className="w-full px-4 py-2.5 rounded-xl border border-gray-300 focus:ring-2 focus:ring-amber-500 focus:border-amber-500 outline-none transition"
/> />
</div> </div>
<div> <div>
<label className="block text-sm font-semibold text-gray-700 mb-1">Instansi / Lembaga</label>
<label className="block text-sm font-semibold text-gray-700 mb-1">
Instansi / Lembaga
</label>
<input <input
type="text" type="text"
value={editFormData.lembaga} value={editFormData.lembaga}
onChange={(e) => setEditFormData({ ...editFormData, lembaga: e.target.value })} onChange={(e) =>
setEditFormData({
...editFormData,
lembaga: e.target.value,
})
}
className="w-full px-4 py-2.5 rounded-xl border border-gray-300 focus:ring-2 focus:ring-amber-500 focus:border-amber-500 outline-none transition" className="w-full px-4 py-2.5 rounded-xl border border-gray-300 focus:ring-2 focus:ring-amber-500 focus:border-amber-500 outline-none transition"
/> />
</div> </div>
<div className="flex justify-end gap-3 pt-4 border-t border-gray-100 mt-6"> <div className="flex justify-end gap-3 pt-4 border-t border-gray-100 mt-6">
<button <button
type="button" type="button"
onClick={() => setIsEditOpen(false)} onClick={() =>
setIsEditOpen(false)
}
className="px-5 py-2.5 rounded-xl border border-gray-300 font-semibold text-gray-600 hover:bg-gray-50 transition" className="px-5 py-2.5 rounded-xl border border-gray-300 font-semibold text-gray-600 hover:bg-gray-50 transition"
> >
Batal Batal
</button> </button>
<button <button
type="submit" type="submit"
disabled={loading} disabled={loading}
className="px-5 py-2.5 rounded-xl bg-amber-600 font-semibold text-white hover:bg-amber-700 shadow transition disabled:opacity-50" className="px-5 py-2.5 rounded-xl bg-amber-600 font-semibold text-white hover:bg-amber-700 shadow transition disabled:opacity-50"
> >
{loading ? "Memproses..." : "Perbarui Pengguna"} {loading
? "Memproses..."
: "Perbarui Pengguna"}
</button> </button>
</div> </div>
</form> </form>
</div> </div>
</div> </div>
)} )}
</div> </div>
); );
} }