442 lines
12 KiB
JavaScript
442 lines
12 KiB
JavaScript
// web/js/admin.js
|
|
// Dashboard Admin JavaScript - FIXED ENDPOINTS
|
|
|
|
let allUsers = [];
|
|
let allCategories = [];
|
|
let allAuditLogs = [];
|
|
|
|
// Initialize dashboard
|
|
window.addEventListener("DOMContentLoaded", async () => {
|
|
const user = checkAuth();
|
|
if (!user || user.role !== "admin") {
|
|
window.location.href = "/login";
|
|
return;
|
|
}
|
|
|
|
await loadStats();
|
|
await loadUsers();
|
|
|
|
setupSearchAndFilters();
|
|
setupReportFilters();
|
|
});
|
|
|
|
// Load statistics - FIXED
|
|
async function loadStats() {
|
|
try {
|
|
const response = await apiCall("/api/admin/dashboard");
|
|
const stats = response.data; // ✅ Access nested data
|
|
|
|
console.log("Stats received:", stats); // DEBUG
|
|
|
|
// ✅ Access nested properties correctly
|
|
document.getElementById("statTotalUsers").textContent =
|
|
stats.users?.total || 0;
|
|
document.getElementById("statTotalItems").textContent =
|
|
stats.items?.total || 0;
|
|
document.getElementById("statTotalClaims").textContent =
|
|
stats.claims?.total || 0;
|
|
document.getElementById("statTotalArchive").textContent =
|
|
stats.archives?.total || 0;
|
|
} catch (error) {
|
|
console.error("Error loading stats:", error);
|
|
showAlert("Gagal memuat statistik", "danger");
|
|
}
|
|
}
|
|
|
|
// Load users - CORRECT (sudah sesuai)
|
|
async function loadUsers() {
|
|
try {
|
|
const response = await apiCall("/api/admin/users");
|
|
allUsers = response.data || [];
|
|
renderUsers(allUsers);
|
|
} catch (error) {
|
|
console.error("Error loading users:", error);
|
|
showAlert("Gagal memuat data user", "danger");
|
|
}
|
|
}
|
|
|
|
// Render users
|
|
function renderUsers(users) {
|
|
const tbody = document.getElementById("usersTableBody");
|
|
|
|
if (!users || users.length === 0) {
|
|
tbody.innerHTML = `
|
|
<tr>
|
|
<td colspan="6" style="text-align: center; padding: 40px; color: #64748b;">
|
|
Belum ada data user
|
|
</td>
|
|
</tr>
|
|
`;
|
|
return;
|
|
}
|
|
|
|
tbody.innerHTML = users
|
|
.map(
|
|
(user) => `
|
|
<tr>
|
|
<td>${user.name}</td>
|
|
<td>${user.email}</td>
|
|
<td>${user.nrp}</td>
|
|
<td>${getRoleBadge(user.role)}</td>
|
|
<td>${getStatusBadge(user.status || "active")}</td>
|
|
<td>
|
|
<button class="btn btn-warning btn-sm" onclick="editUser(${
|
|
user.id
|
|
})">Edit</button>
|
|
${
|
|
user.status === "active"
|
|
? `<button class="btn btn-danger btn-sm" onclick="blockUser(${user.id})">Block</button>`
|
|
: `<button class="btn btn-success btn-sm" onclick="unblockUser(${user.id})">Unblock</button>`
|
|
}
|
|
</td>
|
|
</tr>
|
|
`
|
|
)
|
|
.join("");
|
|
}
|
|
|
|
// Edit user
|
|
async function editUser(userId) {
|
|
try {
|
|
const user = await apiCall(`/api/admin/users/${userId}`);
|
|
|
|
const form = document.getElementById("editUserForm");
|
|
form.elements.user_id.value = user.id;
|
|
form.elements.name.value = user.name;
|
|
form.elements.email.value = user.email;
|
|
form.elements.nrp.value = user.nrp;
|
|
form.elements.phone.value = user.phone || "";
|
|
form.elements.role.value = user.role;
|
|
|
|
openModal("editUserModal");
|
|
} catch (error) {
|
|
console.error("Error loading user:", error);
|
|
showAlert("Gagal memuat data user", "danger");
|
|
}
|
|
}
|
|
|
|
// Submit edit user
|
|
document
|
|
.getElementById("editUserForm")
|
|
?.addEventListener("submit", async (e) => {
|
|
e.preventDefault();
|
|
|
|
const formData = new FormData(e.target);
|
|
const userId = formData.get("user_id");
|
|
const role = formData.get("role");
|
|
|
|
try {
|
|
const submitBtn = e.target.querySelector('button[type="submit"]');
|
|
submitBtn.disabled = true;
|
|
submitBtn.innerHTML = '<span class="loading"></span> Menyimpan...';
|
|
|
|
// Update role
|
|
await apiCall(`/api/admin/users/${userId}/role`, {
|
|
method: "PATCH",
|
|
body: JSON.stringify({ role }),
|
|
});
|
|
|
|
showAlert("User berhasil diupdate!", "success");
|
|
closeModal("editUserModal");
|
|
await loadUsers();
|
|
} catch (error) {
|
|
console.error("Error updating user:", error);
|
|
showAlert(error.message || "Gagal update user", "danger");
|
|
} finally {
|
|
const submitBtn = e.target.querySelector('button[type="submit"]');
|
|
if (submitBtn) {
|
|
submitBtn.disabled = false;
|
|
submitBtn.textContent = "Update User";
|
|
}
|
|
}
|
|
});
|
|
|
|
// Block user
|
|
async function blockUser(userId) {
|
|
if (!confirmAction("Block user ini?")) return;
|
|
|
|
try {
|
|
await apiCall(`/api/admin/users/${userId}/block`, {
|
|
method: "POST",
|
|
});
|
|
|
|
showAlert("User berhasil diblock!", "success");
|
|
await loadUsers();
|
|
} catch (error) {
|
|
console.error("Error blocking user:", error);
|
|
showAlert(error.message || "Gagal block user", "danger");
|
|
}
|
|
}
|
|
|
|
// Unblock user
|
|
async function unblockUser(userId) {
|
|
if (!confirmAction("Unblock user ini?")) return;
|
|
|
|
try {
|
|
await apiCall(`/api/admin/users/${userId}/unblock`, {
|
|
method: "POST",
|
|
});
|
|
|
|
showAlert("User berhasil di-unblock!", "success");
|
|
await loadUsers();
|
|
} catch (error) {
|
|
console.error("Error unblocking user:", error);
|
|
showAlert(error.message || "Gagal unblock user", "danger");
|
|
}
|
|
}
|
|
|
|
// Load categories
|
|
async function loadCategories() {
|
|
try {
|
|
const response = await apiCall("/api/categories");
|
|
allCategories = response.data || [];
|
|
renderCategories(allCategories);
|
|
} catch (error) {
|
|
console.error("Error loading categories:", error);
|
|
showAlert("Gagal memuat data kategori", "danger");
|
|
}
|
|
}
|
|
|
|
// Render categories
|
|
function renderCategories(categories) {
|
|
const grid = document.getElementById("categoriesGrid");
|
|
|
|
if (!categories || categories.length === 0) {
|
|
grid.innerHTML = `
|
|
<div class="empty-state">
|
|
<div class="empty-state-icon">🏷️</div>
|
|
<p>Belum ada kategori</p>
|
|
</div>
|
|
`;
|
|
return;
|
|
}
|
|
|
|
const icons = {
|
|
pakaian: "👕",
|
|
alat_makan: "🍽️",
|
|
aksesoris: "👓",
|
|
elektronik: "💻",
|
|
alat_tulis: "✏️",
|
|
lainnya: "📦",
|
|
};
|
|
|
|
grid.innerHTML = categories
|
|
.map(
|
|
(cat) => `
|
|
<div class="category-card">
|
|
<div class="category-icon">${icons[cat.slug] || "📦"}</div>
|
|
<h3>${cat.name}</h3>
|
|
${
|
|
cat.description
|
|
? `<p style="color: #64748b; font-size: 0.9rem; margin-top: 8px;">${cat.description}</p>`
|
|
: ""
|
|
}
|
|
<div style="display: flex; gap: 8px; margin-top: 15px; justify-content: center;">
|
|
<button class="btn btn-warning btn-sm" onclick="editCategory(${
|
|
cat.id
|
|
})">Edit</button>
|
|
<button class="btn btn-danger btn-sm" onclick="deleteCategory(${
|
|
cat.id
|
|
})">Hapus</button>
|
|
</div>
|
|
</div>
|
|
`
|
|
)
|
|
.join("");
|
|
}
|
|
|
|
// Submit add category
|
|
document
|
|
.getElementById("addCategoryForm")
|
|
?.addEventListener("submit", async (e) => {
|
|
e.preventDefault();
|
|
|
|
const formData = new FormData(e.target);
|
|
const data = Object.fromEntries(formData);
|
|
|
|
try {
|
|
const submitBtn = e.target.querySelector('button[type="submit"]');
|
|
submitBtn.disabled = true;
|
|
submitBtn.innerHTML = '<span class="loading"></span> Menyimpan...';
|
|
|
|
await apiCall("/api/admin/categories", {
|
|
method: "POST",
|
|
body: JSON.stringify(data),
|
|
});
|
|
|
|
showAlert("Kategori berhasil ditambahkan!", "success");
|
|
closeModal("addCategoryModal");
|
|
e.target.reset();
|
|
await loadCategories();
|
|
} catch (error) {
|
|
console.error("Error adding category:", error);
|
|
showAlert(error.message || "Gagal menambahkan kategori", "danger");
|
|
} finally {
|
|
const submitBtn = e.target.querySelector('button[type="submit"]');
|
|
if (submitBtn) {
|
|
submitBtn.disabled = false;
|
|
submitBtn.textContent = "Tambah Kategori";
|
|
}
|
|
}
|
|
});
|
|
|
|
// Edit category
|
|
async function editCategory(catId) {
|
|
const newName = prompt("Nama kategori baru:");
|
|
if (!newName) return;
|
|
|
|
try {
|
|
await apiCall(`/api/admin/categories/${catId}`, {
|
|
method: "PUT",
|
|
body: JSON.stringify({ name: newName }),
|
|
});
|
|
|
|
showAlert("Kategori berhasil diupdate!", "success");
|
|
await loadCategories();
|
|
} catch (error) {
|
|
console.error("Error updating category:", error);
|
|
showAlert(error.message || "Gagal update kategori", "danger");
|
|
}
|
|
}
|
|
|
|
// Delete category
|
|
async function deleteCategory(catId) {
|
|
if (!confirmAction("Hapus kategori ini?")) return;
|
|
|
|
try {
|
|
await apiCall(`/api/admin/categories/${catId}`, {
|
|
method: "DELETE",
|
|
});
|
|
|
|
showAlert("Kategori berhasil dihapus!", "success");
|
|
await loadCategories();
|
|
} catch (error) {
|
|
console.error("Error deleting category:", error);
|
|
showAlert(error.message || "Gagal hapus kategori", "danger");
|
|
}
|
|
}
|
|
|
|
// Setup report filters - SIMPLIFIED (remove preview)
|
|
function setupReportFilters() {
|
|
// Remove preview functionality since endpoint doesn't exist
|
|
// Just setup the export buttons
|
|
}
|
|
|
|
// Export report
|
|
async function exportReport(format) {
|
|
const period = document.getElementById("reportPeriod")?.value;
|
|
const type = document.getElementById("reportType")?.value;
|
|
const startDate = document.getElementById("reportStartDate")?.value;
|
|
const endDate = document.getElementById("reportEndDate")?.value;
|
|
|
|
let url = `/api/admin/reports/export?format=${format}&period=${period}&type=${type}`;
|
|
|
|
if (period === "custom" && startDate && endDate) {
|
|
url += `&start_date=${startDate}&end_date=${endDate}`;
|
|
}
|
|
|
|
try {
|
|
const token = getToken();
|
|
const response = await fetch(`${API_URL}${url}`, {
|
|
headers: {
|
|
Authorization: `Bearer ${token}`,
|
|
},
|
|
});
|
|
|
|
if (!response.ok) {
|
|
throw new Error("Export failed");
|
|
}
|
|
|
|
const blob = await response.blob();
|
|
const downloadUrl = window.URL.createObjectURL(blob);
|
|
const a = document.createElement("a");
|
|
a.href = downloadUrl;
|
|
a.download = `laporan_${type}_${period}.${
|
|
format === "pdf" ? "pdf" : "xlsx"
|
|
}`;
|
|
document.body.appendChild(a);
|
|
a.click();
|
|
window.URL.revokeObjectURL(downloadUrl);
|
|
a.remove();
|
|
|
|
showAlert(
|
|
`Laporan ${format.toUpperCase()} berhasil didownload!`,
|
|
"success"
|
|
);
|
|
} catch (error) {
|
|
console.error("Error exporting report:", error);
|
|
showAlert("Gagal export laporan", "danger");
|
|
}
|
|
}
|
|
|
|
// Load audit logs
|
|
async function loadAudit() {
|
|
try {
|
|
const response = await apiCall("/api/admin/audit-logs");
|
|
allAuditLogs = response.data || [];
|
|
renderAuditLogs(allAuditLogs);
|
|
} catch (error) {
|
|
console.error("Error loading audit logs:", error);
|
|
showAlert("Gagal memuat audit log", "danger");
|
|
}
|
|
}
|
|
|
|
// Render audit logs
|
|
function renderAuditLogs(logs) {
|
|
const tbody = document.getElementById("auditTableBody");
|
|
|
|
if (!logs || logs.length === 0) {
|
|
tbody.innerHTML = `
|
|
<tr>
|
|
<td colspan="5" style="text-align: center; padding: 40px; color: #64748b;">
|
|
Belum ada audit log
|
|
</td>
|
|
</tr>
|
|
`;
|
|
return;
|
|
}
|
|
|
|
tbody.innerHTML = logs
|
|
.map(
|
|
(log) => `
|
|
<tr>
|
|
<td>${formatDateTime(log.created_at)}</td>
|
|
<td>${log.user_name}</td>
|
|
<td><span class="badge badge-primary">${log.action}</span></td>
|
|
<td>${log.details || "-"}</td>
|
|
<td>${log.ip_address || "-"}</td>
|
|
</tr>
|
|
`
|
|
)
|
|
.join("");
|
|
}
|
|
|
|
// Setup search and filters
|
|
function setupSearchAndFilters() {
|
|
const searchUsers = document.getElementById("searchUsers");
|
|
const roleFilter = document.getElementById("roleFilter");
|
|
const statusFilter = document.getElementById("statusFilter");
|
|
|
|
const performUsersSearch = debounce(() => {
|
|
const searchTerm = searchUsers?.value.toLowerCase() || "";
|
|
const role = roleFilter?.value || "";
|
|
const status = statusFilter?.value || "";
|
|
|
|
let filtered = allUsers.filter((user) => {
|
|
const matchesSearch =
|
|
user.name.toLowerCase().includes(searchTerm) ||
|
|
user.email.toLowerCase().includes(searchTerm) ||
|
|
user.nrp.includes(searchTerm);
|
|
const matchesRole = !role || user.role === role;
|
|
const matchesStatus = !status || user.status === status;
|
|
return matchesSearch && matchesRole && matchesStatus;
|
|
});
|
|
|
|
renderUsers(filtered);
|
|
}, 300);
|
|
|
|
searchUsers?.addEventListener("input", performUsersSearch);
|
|
roleFilter?.addEventListener("change", performUsersSearch);
|
|
statusFilter?.addEventListener("change", performUsersSearch);
|
|
}
|