491 lines
14 KiB
JavaScript
491 lines
14 KiB
JavaScript
// Dashboard User JavaScript - FIXED ENDPOINTS
|
|
|
|
let allItems = [];
|
|
let allLostItems = [];
|
|
let allClaims = [];
|
|
|
|
// Initialize dashboard
|
|
window.addEventListener("DOMContentLoaded", async () => {
|
|
const user = checkAuth();
|
|
if (!user || user.role !== "user") {
|
|
window.location.href = "/login";
|
|
return;
|
|
}
|
|
|
|
await loadStats();
|
|
await loadBrowseItems();
|
|
setupSearchAndFilters();
|
|
});
|
|
|
|
// Load statistics - CORRECT (sudah sesuai)
|
|
async function loadStats() {
|
|
try {
|
|
const stats = await apiCall("/api/user/stats");
|
|
document.getElementById("statLost").textContent = stats.lost_items || 0;
|
|
document.getElementById("statFound").textContent = stats.found_items || 0;
|
|
document.getElementById("statClaims").textContent = stats.claims || 0;
|
|
} catch (error) {
|
|
console.error("Error loading stats:", error);
|
|
}
|
|
}
|
|
|
|
// Load browse items - CORRECT (sudah sesuai)
|
|
async function loadBrowseItems() {
|
|
setLoading("itemsGrid", true);
|
|
|
|
try {
|
|
const response = await apiCall("/api/items");
|
|
allItems = response.data || [];
|
|
renderItems(allItems);
|
|
} catch (error) {
|
|
console.error("Error loading items:", error);
|
|
showEmptyState("itemsGrid", "📦", "Gagal memuat data barang");
|
|
}
|
|
}
|
|
|
|
// Render items
|
|
function renderItems(items) {
|
|
const grid = document.getElementById("itemsGrid");
|
|
|
|
if (!items || items.length === 0) {
|
|
showEmptyState("itemsGrid", "📦", "Belum ada barang ditemukan");
|
|
return;
|
|
}
|
|
|
|
grid.innerHTML = items
|
|
.map(
|
|
(item) => `
|
|
<div class="item-card" onclick="viewItemDetail(${item.id})">
|
|
<img src="${
|
|
item.photo_url || "https://via.placeholder.com/280x200?text=No+Image"
|
|
}"
|
|
alt="${item.name}"
|
|
class="item-image"
|
|
onerror="this.src='https://via.placeholder.com/280x200?text=No+Image'">
|
|
<div class="item-body">
|
|
<h3 class="item-title">${item.name}</h3>
|
|
<div class="item-meta">
|
|
<span>📍 ${item.location}</span>
|
|
<span>📅 ${formatDate(item.date_found)}</span>
|
|
<span>${getStatusBadge(item.status)}</span>
|
|
</div>
|
|
${
|
|
item.status === "unclaimed"
|
|
? `<button class="btn btn-primary btn-sm" onclick="event.stopPropagation(); openClaimModal(${item.id})">Klaim Barang</button>`
|
|
: ""
|
|
}
|
|
</div>
|
|
</div>
|
|
`
|
|
)
|
|
.join("");
|
|
}
|
|
|
|
// View item detail - CORRECT (sudah sesuai)
|
|
async function viewItemDetail(itemId) {
|
|
try {
|
|
const item = await apiCall(`/api/items/${itemId}`);
|
|
|
|
const modalContent = document.getElementById("itemDetailContent");
|
|
modalContent.innerHTML = `
|
|
<img src="${
|
|
item.photo_url || "https://via.placeholder.com/600x400?text=No+Image"
|
|
}"
|
|
alt="${item.name}"
|
|
style="width: 100%; max-height: 400px; object-fit: cover; border-radius: 10px; margin-bottom: 20px;"
|
|
onerror="this.src='https://via.placeholder.com/600x400?text=No+Image'">
|
|
<h3>${item.name}</h3>
|
|
<div style="display: grid; gap: 15px; margin-top: 20px;">
|
|
<div><strong>Kategori:</strong> ${item.category}</div>
|
|
<div><strong>Lokasi Ditemukan:</strong> ${item.location}</div>
|
|
<div><strong>Tanggal Ditemukan:</strong> ${formatDate(
|
|
item.date_found
|
|
)}</div>
|
|
<div><strong>Status:</strong> ${getStatusBadge(item.status)}</div>
|
|
</div>
|
|
${
|
|
item.status === "unclaimed"
|
|
? `<button class="btn btn-primary" onclick="openClaimModal(${item.id})" style="width: 100%; margin-top: 20px;">Klaim Barang Ini</button>`
|
|
: ""
|
|
}
|
|
`;
|
|
|
|
openModal("itemDetailModal");
|
|
} catch (error) {
|
|
console.error("Error loading item detail:", error);
|
|
showAlert("Gagal memuat detail barang", "danger");
|
|
}
|
|
}
|
|
|
|
// Open claim modal
|
|
function openClaimModal(itemId) {
|
|
closeModal("itemDetailModal");
|
|
|
|
const modalContent = document.getElementById("claimModalContent");
|
|
modalContent.innerHTML = `
|
|
<form id="claimForm" onsubmit="submitClaim(event, ${itemId})">
|
|
<div class="form-group">
|
|
<label>Deskripsi Barang (Jelaskan ciri-ciri barang Anda) *</label>
|
|
<textarea name="description" required placeholder="Jelaskan ciri khusus barang yang hanya Anda tahu..."></textarea>
|
|
</div>
|
|
<div class="form-group">
|
|
<label>Bukti Pendukung (Opsional)</label>
|
|
<input type="file" name="proof" accept="image/*">
|
|
<small style="color: #64748b;">Upload foto barang saat Anda masih memilikinya (opsional)</small>
|
|
</div>
|
|
<div class="form-group">
|
|
<label>No. Kontak *</label>
|
|
<input type="text" name="contact" required placeholder="08123456789">
|
|
</div>
|
|
<button type="submit" class="btn btn-primary" style="width: 100%;">Submit Klaim</button>
|
|
</form>
|
|
`;
|
|
|
|
openModal("claimModal");
|
|
}
|
|
|
|
// Submit claim - CORRECT (sudah sesuai)
|
|
async function submitClaim(event, itemId) {
|
|
event.preventDefault();
|
|
|
|
const form = event.target;
|
|
const formData = new FormData(form);
|
|
formData.append("item_id", itemId);
|
|
|
|
try {
|
|
const submitBtn = form.querySelector('button[type="submit"]');
|
|
submitBtn.disabled = true;
|
|
submitBtn.innerHTML = '<span class="loading"></span> Mengirim...';
|
|
|
|
await apiUpload("/api/claims", formData);
|
|
|
|
showAlert(
|
|
"Klaim berhasil disubmit! Menunggu verifikasi dari manager.",
|
|
"success"
|
|
);
|
|
closeModal("claimModal");
|
|
await loadBrowseItems();
|
|
await loadStats();
|
|
} catch (error) {
|
|
console.error("Error submitting claim:", error);
|
|
showAlert(error.message || "Gagal submit klaim", "danger");
|
|
} finally {
|
|
const submitBtn = form.querySelector('button[type="submit"]');
|
|
if (submitBtn) {
|
|
submitBtn.disabled = false;
|
|
submitBtn.textContent = "Submit Klaim";
|
|
}
|
|
}
|
|
}
|
|
|
|
// Load my lost items - CORRECT (sudah sesuai)
|
|
async function loadLost() {
|
|
setLoading("lostItemsGrid", true);
|
|
|
|
try {
|
|
const response = await apiCall("/api/user/lost-items");
|
|
allLostItems = response.data || [];
|
|
renderLostItems(allLostItems);
|
|
} catch (error) {
|
|
console.error("Error loading lost items:", error);
|
|
showEmptyState("lostItemsGrid", "😢", "Gagal memuat data barang hilang");
|
|
}
|
|
}
|
|
|
|
// Render lost items
|
|
function renderLostItems(items) {
|
|
const grid = document.getElementById("lostItemsGrid");
|
|
|
|
if (!items || items.length === 0) {
|
|
showEmptyState(
|
|
"lostItemsGrid",
|
|
"😢",
|
|
"Anda belum melaporkan barang hilang"
|
|
);
|
|
return;
|
|
}
|
|
|
|
grid.innerHTML = items
|
|
.map(
|
|
(item) => `
|
|
<div class="item-card">
|
|
<div class="item-body">
|
|
<h3 class="item-title">${item.name}</h3>
|
|
<div class="item-meta">
|
|
<span>🏷️ ${item.category}</span>
|
|
<span>🎨 ${item.color}</span>
|
|
<span>📅 ${formatDate(item.date_lost)}</span>
|
|
${item.location ? `<span>📍 ${item.location}</span>` : ""}
|
|
</div>
|
|
<p style="color: #64748b; font-size: 0.9rem; margin-top: 10px;">${
|
|
item.description
|
|
}</p>
|
|
</div>
|
|
</div>
|
|
`
|
|
)
|
|
.join("");
|
|
}
|
|
|
|
// Load my found items - FIXED
|
|
async function loadFound() {
|
|
setLoading("foundItemsGrid", true);
|
|
|
|
try {
|
|
const response = await apiCall("/api/user/items");
|
|
const items = response.data || [];
|
|
renderFoundItems(items);
|
|
} catch (error) {
|
|
console.error("Error loading found items:", error);
|
|
showEmptyState(
|
|
"foundItemsGrid",
|
|
"🎉",
|
|
"Gagal memuat data barang yang ditemukan"
|
|
);
|
|
}
|
|
}
|
|
|
|
// Render found items
|
|
function renderFoundItems(items) {
|
|
const grid = document.getElementById("foundItemsGrid");
|
|
|
|
if (!items || items.length === 0) {
|
|
showEmptyState(
|
|
"foundItemsGrid",
|
|
"🎉",
|
|
"Anda belum melaporkan penemuan barang"
|
|
);
|
|
return;
|
|
}
|
|
|
|
grid.innerHTML = items
|
|
.map(
|
|
(item) => `
|
|
<div class="item-card">
|
|
<img src="${
|
|
item.photo_url || "https://via.placeholder.com/280x200?text=No+Image"
|
|
}"
|
|
alt="${item.name}"
|
|
class="item-image"
|
|
onerror="this.src='https://via.placeholder.com/280x200?text=No+Image'">
|
|
<div class="item-body">
|
|
<h3 class="item-title">${item.name}</h3>
|
|
<div class="item-meta">
|
|
<span>📍 ${item.location}</span>
|
|
<span>📅 ${formatDate(item.date_found)}</span>
|
|
<span>${getStatusBadge(item.status)}</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
`
|
|
)
|
|
.join("");
|
|
}
|
|
|
|
// Load my claims - CORRECT (sudah sesuai)
|
|
async function loadClaims() {
|
|
setLoading("claimsGrid", true);
|
|
|
|
try {
|
|
const response = await apiCall("/api/user/claims");
|
|
allClaims = response.data || [];
|
|
renderClaims(allClaims);
|
|
} catch (error) {
|
|
console.error("Error loading claims:", error);
|
|
showEmptyState("claimsGrid", "🤝", "Gagal memuat data klaim");
|
|
}
|
|
}
|
|
|
|
// Render claims
|
|
function renderClaims(claims) {
|
|
const grid = document.getElementById("claimsGrid");
|
|
|
|
if (!claims || claims.length === 0) {
|
|
showEmptyState("claimsGrid", "🤝", "Anda belum pernah mengajukan klaim");
|
|
return;
|
|
}
|
|
|
|
grid.innerHTML = claims
|
|
.map(
|
|
(claim) => `
|
|
<div class="item-card">
|
|
<div class="item-body">
|
|
<h3 class="item-title">${claim.item_name}</h3>
|
|
<div class="item-meta">
|
|
<span>📅 ${formatDate(claim.created_at)}</span>
|
|
<span>${getStatusBadge(claim.status)}</span>
|
|
</div>
|
|
<p style="color: #64748b; font-size: 0.9rem; margin-top: 10px;">
|
|
${claim.description}
|
|
</p>
|
|
${
|
|
claim.status === "rejected" && claim.notes
|
|
? `
|
|
<p style="color: #ef4444; font-size: 0.9rem; margin-top: 10px;">
|
|
<strong>Alasan ditolak:</strong> ${claim.notes}
|
|
</p>
|
|
`
|
|
: ""
|
|
}
|
|
</div>
|
|
</div>
|
|
`
|
|
)
|
|
.join("");
|
|
}
|
|
|
|
// Report lost item
|
|
function openReportLostModal() {
|
|
openModal("reportLostModal");
|
|
}
|
|
|
|
// Submit lost item report - CORRECT (sudah sesuai)
|
|
document
|
|
.getElementById("reportLostForm")
|
|
?.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> Mengirim...';
|
|
|
|
await apiCall("/api/lost-items", {
|
|
method: "POST",
|
|
body: JSON.stringify(data),
|
|
});
|
|
|
|
showAlert("Laporan kehilangan berhasil disubmit!", "success");
|
|
closeModal("reportLostModal");
|
|
e.target.reset();
|
|
await loadLost();
|
|
await loadStats();
|
|
} catch (error) {
|
|
console.error("Error submitting lost item:", error);
|
|
showAlert(error.message || "Gagal submit laporan", "danger");
|
|
} finally {
|
|
const submitBtn = e.target.querySelector('button[type="submit"]');
|
|
if (submitBtn) {
|
|
submitBtn.disabled = false;
|
|
submitBtn.textContent = "Submit Laporan";
|
|
}
|
|
}
|
|
});
|
|
|
|
// Report found item
|
|
function openReportFoundModal() {
|
|
openModal("reportFoundModal");
|
|
}
|
|
|
|
// Submit found item report - CORRECT (sudah sesuai)
|
|
document
|
|
.getElementById("reportFoundForm")
|
|
?.addEventListener("submit", async (e) => {
|
|
e.preventDefault();
|
|
|
|
const formData = new FormData(e.target);
|
|
|
|
try {
|
|
const submitBtn = e.target.querySelector('button[type="submit"]');
|
|
submitBtn.disabled = true;
|
|
submitBtn.innerHTML = '<span class="loading"></span> Mengirim...';
|
|
|
|
await apiUpload("/api/items", formData);
|
|
|
|
showAlert("Laporan penemuan berhasil disubmit!", "success");
|
|
closeModal("reportFoundModal");
|
|
e.target.reset();
|
|
await loadFound();
|
|
await loadStats();
|
|
} catch (error) {
|
|
console.error("Error submitting found item:", error);
|
|
showAlert(error.message || "Gagal submit laporan", "danger");
|
|
} finally {
|
|
const submitBtn = e.target.querySelector('button[type="submit"]');
|
|
if (submitBtn) {
|
|
submitBtn.disabled = false;
|
|
submitBtn.textContent = "Submit Penemuan";
|
|
}
|
|
}
|
|
});
|
|
|
|
// Setup search and filters
|
|
function setupSearchAndFilters() {
|
|
const searchInput = document.getElementById("searchInput");
|
|
const categoryFilter = document.getElementById("categoryFilter");
|
|
const sortBy = document.getElementById("sortBy");
|
|
|
|
const performSearch = debounce(() => {
|
|
const searchTerm = searchInput?.value.toLowerCase() || "";
|
|
const category = categoryFilter?.value || "";
|
|
const sort = sortBy?.value || "date_desc";
|
|
|
|
let filtered = allItems.filter((item) => {
|
|
const matchesSearch =
|
|
item.name.toLowerCase().includes(searchTerm) ||
|
|
item.location.toLowerCase().includes(searchTerm);
|
|
const matchesCategory = !category || item.category === category;
|
|
return matchesSearch && matchesCategory;
|
|
});
|
|
|
|
// Sort
|
|
filtered.sort((a, b) => {
|
|
switch (sort) {
|
|
case "date_desc":
|
|
return new Date(b.date_found) - new Date(a.date_found);
|
|
case "date_asc":
|
|
return new Date(a.date_found) - new Date(b.date_found);
|
|
case "name_asc":
|
|
return a.name.localeCompare(b.name);
|
|
case "name_desc":
|
|
return b.name.localeCompare(a.name);
|
|
default:
|
|
return 0;
|
|
}
|
|
});
|
|
|
|
renderItems(filtered);
|
|
}, 300);
|
|
|
|
searchInput?.addEventListener("input", performSearch);
|
|
categoryFilter?.addEventListener("change", performSearch);
|
|
sortBy?.addEventListener("change", performSearch);
|
|
}
|
|
|
|
// Create claim modal if not exists
|
|
if (!document.getElementById("claimModal")) {
|
|
const claimModal = document.createElement("div");
|
|
claimModal.id = "claimModal";
|
|
claimModal.className = "modal";
|
|
claimModal.innerHTML = `
|
|
<div class="modal-content">
|
|
<div class="modal-header">
|
|
<h3 class="modal-title">Klaim Barang</h3>
|
|
<button class="close-btn" onclick="closeModal('claimModal')">×</button>
|
|
</div>
|
|
<div class="modal-body" id="claimModalContent"></div>
|
|
</div>
|
|
`;
|
|
document.body.appendChild(claimModal);
|
|
}
|
|
|
|
// Create item detail modal if not exists
|
|
if (!document.getElementById("itemDetailModal")) {
|
|
const itemDetailModal = document.createElement("div");
|
|
itemDetailModal.id = "itemDetailModal";
|
|
itemDetailModal.className = "modal";
|
|
itemDetailModal.innerHTML = `
|
|
<div class="modal-content modal-large">
|
|
<div class="modal-header">
|
|
<h3 class="modal-title">Detail Barang</h3>
|
|
<button class="close-btn" onclick="closeModal('itemDetailModal')">×</button>
|
|
</div>
|
|
<div class="modal-body" id="itemDetailContent"></div>
|
|
</div>
|
|
`;
|
|
document.body.appendChild(itemDetailModal);
|
|
}
|