350 lines
8.8 KiB
JavaScript
350 lines
8.8 KiB
JavaScript
// Main.js - Shared functions across all dashboards
|
|
const API_URL = "http://localhost:8080";
|
|
|
|
// Auth utilities
|
|
function getToken() {
|
|
return localStorage.getItem("token");
|
|
}
|
|
|
|
function getCurrentUser() {
|
|
const user = localStorage.getItem("user");
|
|
return user ? JSON.parse(user) : null;
|
|
}
|
|
|
|
function setAuth(token, user) {
|
|
localStorage.setItem("token", token);
|
|
localStorage.setItem("user", JSON.stringify(user));
|
|
}
|
|
|
|
function clearAuth() {
|
|
localStorage.removeItem("token");
|
|
localStorage.removeItem("user");
|
|
}
|
|
|
|
// Check authentication - FIXED
|
|
function checkAuth() {
|
|
const token = getToken();
|
|
const user = getCurrentUser();
|
|
|
|
if (!token || !user) {
|
|
window.location.href = "/login"; // FIXED: tambah /
|
|
return null;
|
|
}
|
|
|
|
return user;
|
|
}
|
|
|
|
// Logout - FIXED
|
|
function logout() {
|
|
if (confirm("Apakah Anda yakin ingin logout?")) {
|
|
clearAuth();
|
|
window.location.href = "/login"; // FIXED: tambah /
|
|
}
|
|
}
|
|
|
|
// API call helper
|
|
async function apiCall(endpoint, options = {}) {
|
|
const token = getToken();
|
|
|
|
const defaultOptions = {
|
|
headers: {
|
|
"Content-Type": "application/json",
|
|
...(token && { Authorization: `Bearer ${token}` }),
|
|
},
|
|
};
|
|
|
|
const finalOptions = {
|
|
...defaultOptions,
|
|
...options,
|
|
headers: {
|
|
...defaultOptions.headers,
|
|
...options.headers,
|
|
},
|
|
};
|
|
|
|
try {
|
|
const response = await fetch(`${API_URL}${endpoint}`, finalOptions);
|
|
const data = await response.json();
|
|
|
|
if (!response.ok) {
|
|
// Handle 401 Unauthorized
|
|
if (response.status === 401) {
|
|
showAlert("Session expired. Please login again.", "danger");
|
|
setTimeout(() => {
|
|
clearAuth();
|
|
window.location.href = "/login"; // FIXED: tambah /
|
|
}, 1500);
|
|
throw new Error("Unauthorized");
|
|
}
|
|
|
|
throw new Error(data.error || "Request failed");
|
|
}
|
|
|
|
return data;
|
|
} catch (error) {
|
|
console.error("API call error:", error);
|
|
throw error;
|
|
}
|
|
}
|
|
|
|
// API call for file upload - FIXED: support PUT method
|
|
async function apiUpload(endpoint, formData, method = "POST") {
|
|
const token = getToken();
|
|
|
|
try {
|
|
const response = await fetch(`${API_URL}${endpoint}`, {
|
|
method: method, // FIXED: bisa POST atau PUT
|
|
headers: {
|
|
...(token && { Authorization: `Bearer ${token}` }),
|
|
// JANGAN set Content-Type untuk FormData, browser akan set otomatis dengan boundary
|
|
},
|
|
body: formData,
|
|
});
|
|
|
|
const data = await response.json();
|
|
|
|
if (!response.ok) {
|
|
if (response.status === 401) {
|
|
showAlert("Session expired. Please login again.", "danger");
|
|
setTimeout(() => {
|
|
clearAuth();
|
|
window.location.href = "/login"; // FIXED: tambah /
|
|
}, 1500);
|
|
throw new Error("Unauthorized");
|
|
}
|
|
|
|
throw new Error(data.error || "Upload failed");
|
|
}
|
|
|
|
return data;
|
|
} catch (error) {
|
|
console.error("Upload error:", error);
|
|
throw error;
|
|
}
|
|
}
|
|
|
|
// Tab switching
|
|
function switchTab(tabName) {
|
|
// Remove active class from all tabs
|
|
document.querySelectorAll(".tab-btn").forEach((btn) => {
|
|
btn.classList.remove("active");
|
|
});
|
|
document.querySelectorAll(".tab-content").forEach((content) => {
|
|
content.classList.remove("active");
|
|
});
|
|
|
|
// Add active class to selected tab
|
|
event.target.classList.add("active");
|
|
document.getElementById(tabName + "Tab").classList.add("active");
|
|
|
|
// Trigger load function for specific tab if exists
|
|
const loadFunctionName = `load${capitalize(tabName)}`;
|
|
if (typeof window[loadFunctionName] === "function") {
|
|
window[loadFunctionName]();
|
|
}
|
|
}
|
|
|
|
// Modal utilities
|
|
function openModal(modalId) {
|
|
document.getElementById(modalId).classList.add("active");
|
|
}
|
|
|
|
function closeModal(modalId) {
|
|
document.getElementById(modalId).classList.remove("active");
|
|
}
|
|
|
|
// Close modal when clicking outside
|
|
window.addEventListener("click", (e) => {
|
|
if (e.target.classList.contains("modal")) {
|
|
e.target.classList.remove("active");
|
|
}
|
|
});
|
|
|
|
// Alert notification
|
|
function showAlert(message, type = "info") {
|
|
const alertDiv = document.createElement("div");
|
|
alertDiv.className = `alert alert-${type}`;
|
|
alertDiv.textContent = message;
|
|
alertDiv.style.cssText = `
|
|
position: fixed;
|
|
top: 80px;
|
|
right: 20px;
|
|
padding: 15px 20px;
|
|
border-radius: 10px;
|
|
box-shadow: 0 5px 15px rgba(0,0,0,0.2);
|
|
z-index: 9999;
|
|
animation: slideInRight 0.3s;
|
|
`;
|
|
|
|
// Set colors based on type
|
|
const colors = {
|
|
success: { bg: "#d1fae5", color: "#10b981", border: "#10b981" },
|
|
danger: { bg: "#fee2e2", color: "#ef4444", border: "#ef4444" },
|
|
warning: { bg: "#fef3c7", color: "#f59e0b", border: "#f59e0b" },
|
|
info: { bg: "#dbeafe", color: "#2563eb", border: "#2563eb" },
|
|
};
|
|
|
|
const colorScheme = colors[type] || colors.info;
|
|
alertDiv.style.background = colorScheme.bg;
|
|
alertDiv.style.color = colorScheme.color;
|
|
alertDiv.style.border = `2px solid ${colorScheme.border}`;
|
|
|
|
document.body.appendChild(alertDiv);
|
|
|
|
setTimeout(() => {
|
|
alertDiv.style.animation = "slideOutRight 0.3s";
|
|
setTimeout(() => alertDiv.remove(), 300);
|
|
}, 3000);
|
|
}
|
|
|
|
// Format date
|
|
function formatDate(dateString) {
|
|
const date = new Date(dateString);
|
|
const options = { year: "numeric", month: "long", day: "numeric" };
|
|
return date.toLocaleDateString("id-ID", options);
|
|
}
|
|
|
|
// Format datetime
|
|
function formatDateTime(dateString) {
|
|
const date = new Date(dateString);
|
|
const options = {
|
|
year: "numeric",
|
|
month: "long",
|
|
day: "numeric",
|
|
hour: "2-digit",
|
|
minute: "2-digit",
|
|
};
|
|
return date.toLocaleDateString("id-ID", options);
|
|
}
|
|
|
|
// Capitalize first letter
|
|
function capitalize(str) {
|
|
return str.charAt(0).toUpperCase() + str.slice(1);
|
|
}
|
|
|
|
// Get status badge HTML
|
|
function getStatusBadge(status) {
|
|
const statusMap = {
|
|
unclaimed: { label: "Unclaimed", class: "badge-primary" },
|
|
pending_claim: { label: "Pending Claim", class: "badge-warning" },
|
|
verified: { label: "Verified", class: "badge-success" },
|
|
case_closed: { label: "Case Closed", class: "badge-success" },
|
|
expired: { label: "Expired", class: "badge-danger" },
|
|
pending: { label: "Pending", class: "badge-warning" },
|
|
approved: { label: "Approved", class: "badge-success" },
|
|
rejected: { label: "Rejected", class: "badge-danger" },
|
|
active: { label: "Active", class: "badge-success" },
|
|
blocked: { label: "Blocked", class: "badge-danger" },
|
|
};
|
|
|
|
const statusInfo = statusMap[status] || {
|
|
label: status,
|
|
class: "badge-primary",
|
|
};
|
|
return `<span class="badge ${statusInfo.class}">${statusInfo.label}</span>`;
|
|
}
|
|
|
|
// Get role badge HTML
|
|
function getRoleBadge(role) {
|
|
const roleMap = {
|
|
admin: { label: "Admin", class: "badge-danger" },
|
|
manager: { label: "Manager", class: "badge-warning" },
|
|
user: { label: "User", class: "badge-primary" },
|
|
};
|
|
|
|
const roleInfo = roleMap[role] || { label: role, class: "badge-primary" };
|
|
return `<span class="badge ${roleInfo.class}">${roleInfo.label}</span>`;
|
|
}
|
|
|
|
// Confirm dialog
|
|
function confirmAction(message) {
|
|
return confirm(message);
|
|
}
|
|
|
|
// Loading state
|
|
function setLoading(elementId, isLoading) {
|
|
const element = document.getElementById(elementId);
|
|
if (!element) return;
|
|
|
|
if (isLoading) {
|
|
element.innerHTML = `
|
|
<div class="empty-state">
|
|
<div class="loading" style="width: 50px; height: 50px; border-width: 5px;"></div>
|
|
<p style="margin-top: 20px;">Loading...</p>
|
|
</div>
|
|
`;
|
|
}
|
|
}
|
|
|
|
// Empty state
|
|
function showEmptyState(elementId, icon, message) {
|
|
const element = document.getElementById(elementId);
|
|
if (!element) return;
|
|
|
|
element.innerHTML = `
|
|
<div class="empty-state">
|
|
<div class="empty-state-icon">${icon}</div>
|
|
<h3>Tidak ada data</h3>
|
|
<p>${message}</p>
|
|
</div>
|
|
`;
|
|
}
|
|
|
|
// Debounce function for search
|
|
function debounce(func, wait) {
|
|
let timeout;
|
|
return function executedFunction(...args) {
|
|
const later = () => {
|
|
clearTimeout(timeout);
|
|
func(...args);
|
|
};
|
|
clearTimeout(timeout);
|
|
timeout = setTimeout(later, wait);
|
|
};
|
|
}
|
|
|
|
// Add CSS animation styles
|
|
const style = document.createElement("style");
|
|
style.textContent = `
|
|
@keyframes slideInRight {
|
|
from {
|
|
transform: translateX(100%);
|
|
opacity: 0;
|
|
}
|
|
to {
|
|
transform: translateX(0);
|
|
opacity: 1;
|
|
}
|
|
}
|
|
|
|
@keyframes slideOutRight {
|
|
from {
|
|
transform: translateX(0);
|
|
opacity: 1;
|
|
}
|
|
to {
|
|
transform: translateX(100%);
|
|
opacity: 0;
|
|
}
|
|
}
|
|
`;
|
|
document.head.appendChild(style);
|
|
|
|
// Initialize user info in navbar
|
|
function initUserInfo() {
|
|
const user = getCurrentUser();
|
|
if (!user) return;
|
|
|
|
const userNameEl = document.getElementById("userName");
|
|
const userAvatarEl = document.getElementById("userAvatar");
|
|
|
|
if (userNameEl) userNameEl.textContent = user.name;
|
|
if (userAvatarEl)
|
|
userAvatarEl.textContent = user.name.charAt(0).toUpperCase();
|
|
}
|
|
|
|
// Initialize on page load
|
|
window.addEventListener("DOMContentLoaded", () => {
|
|
initUserInfo();
|
|
});
|