async function loadDashboardMetrics() {
const metricsData = {
totalReports: null,
activeIssues: null,
resolvedIssues: null,
criticalAlerts: null,
};
const safeSetText = (selector, value) => {
const el = document.querySelector(selector);
if (el) el.textContent = value !== null ? value : "—";
};
safeSetText('[data-value="total-reports"]', metricsData.totalReports);
safeSetText('[data-value="active-issues"]', metricsData.activeIssues);
safeSetText('[data-value="resolved-issues"]', metricsData.resolvedIssues);
safeSetText('[data-value="critical-alerts"]', metricsData.criticalAlerts);
}
async function loadRecentActivity() {
const activities = [];
const listContainer = document.getElementById("activity-list");
if (!listContainer) return;
const skeleton = document.getElementById("activity-skeleton");
const empty = document.getElementById("activity-empty");
const items = document.getElementById("activity-items");
if (skeleton) skeleton.classList.add("hidden");
if (activities.length === 0) {
listContainer.setAttribute("data-status", "empty");
if (empty) empty.classList.remove("hidden");
} else {
listContainer.setAttribute("data-status", "loaded");
if (items) {
items.classList.remove("hidden");
activities.forEach((activity) => {
const item = createActivityItem(activity);
items.appendChild(item);
});
}
}
}
function createActivityItem(activity) {
const div = document.createElement("div");
div.className = "px-6 py-4 flex items-start space-x-4";
div.setAttribute("data-activity-id", activity.id);
const statusColors = {
critical: "bg-red-400",
warning: "bg-yellow-400",
info: "bg-blue-400",
success: "bg-green-400",
};
div.innerHTML = `
${escapeHtml(activity.message)}
${escapeHtml(
activity.timestamp
)}
${escapeHtml(activity.status)}
`;
return div;
}
function getBadgeClass(status) {
const classes = {
new: "bg-blue-900/40 text-blue-400",
"in-progress": "bg-yellow-900/40 text-yellow-400",
resolved: "bg-green-900/40 text-green-400",
closed: "bg-gray-800 text-gray-400",
};
return classes[status] || "bg-gray-800 text-gray-400";
}
function switchTab(tabName) {
const reportsView = document.getElementById("view-reports");
const geminiView = document.getElementById("view-gemini");
if (!reportsView || !geminiView) return;
reportsView.classList.add("hidden");
geminiView.classList.add("hidden");
const inactiveClass =
"px-4 py-2 text-sm font-medium rounded-lg text-neutral-400 hover:text-white hover:bg-neutral-800 transition-all cursor-pointer flex items-center gap-2";
const activeClass =
"px-4 py-2 text-sm font-medium rounded-lg bg-neutral-800 text-white shadow-sm border border-neutral-700 transition-all cursor-default flex items-center gap-2";
const btnReports = document.getElementById("tab-btn-reports");
const btnGemini = document.getElementById("tab-btn-gemini");
if (btnReports) btnReports.className = inactiveClass;
if (btnGemini) btnGemini.className = inactiveClass;
document.getElementById("view-" + tabName).classList.remove("hidden");
if (tabName === "reports") {
if (btnReports) btnReports.className = activeClass;
} else {
if (btnGemini) btnGemini.className = activeClass;
setTimeout(() => {
const input = document.getElementById("gemini-prompt");
if (input) {
input.focus();
input.style.height = "auto";
input.style.height = input.scrollHeight + "px";
}
}, 100);
}
}
function setStatusAndSubmit(statusVal) {
const input = document.getElementById("statusInput");
const form = document.getElementById("filterForm");
if (input && form) {
input.value = statusVal;
form.submit();
}
}
function askAboutReport(id, title, category) {
switchTab("gemini");
const inputField = document.getElementById("gemini-prompt");
if (inputField) {
inputField.value = `Regarding Report #${id} ("${title}") in category "${category}":
As a student, what can I personally do right now while this issue is unresolved?
Structure your answer with these exact headers(keep bullet points short and concise):
1. Precautions
2. Alternatives
3. Avoid
4. Awareness`;
inputField.style.height = "auto";
inputField.style.height = inputField.scrollHeight + "px";
inputField.focus();
}
}
function validateFile(input) {
const file = input.files[0];
const labelMain = document.getElementById("file-label-main");
const labelSub = document.getElementById("file-label-sub");
const previewIcon = document.getElementById("file-icon");
if (!file) {
labelMain.innerText = "Click to upload or drag and drop";
labelSub.innerText = "PNG, JPG, JPEG (max. 2MB)";
labelSub.classList.remove("text-red-500");
previewIcon.classList.remove("text-blue-500");
previewIcon.classList.add("text-neutral-400");
return;
}
const maxSize = 2 * 1024 * 1024; // 2MB
if (file.size > maxSize) {
alert("File is too large! Please select an image under 2MB.");
input.value = "";
labelMain.innerText = "File too large!";
labelSub.innerText = "Please select a file under 2MB";
labelSub.classList.add("text-red-500");
previewIcon.classList.remove("text-blue-500");
previewIcon.classList.add("text-neutral-400");
return;
}
labelMain.innerText = file.name;
labelSub.innerText = (file.size / 1024 / 1024).toFixed(2) + " MB";
labelSub.classList.remove("text-red-500");
previewIcon.classList.add("text-blue-500");
previewIcon.classList.remove("text-neutral-400");
}
function useCurrentLocation() {
if (!navigator.geolocation) {
alert("Geolocation is not supported by your browser.");
return;
}
const locInput = document.getElementById("locationInput");
if (!locInput) return;
locInput.placeholder = "Fetching location...";
navigator.geolocation.getCurrentPosition(
function (position) {
const lat = position.coords.latitude;
const lng = position.coords.longitude;
locInput.value = lat + ", " + lng;
},
function (error) {
alert("Unable to retrieve your location.");
locInput.placeholder = "Paste Google Maps link or enter address...";
},
{ enableHighAccuracy: true, timeout: 10000 }
);
}
document.addEventListener("DOMContentLoaded", () => {
loadDashboardMetrics();
setTimeout(() => loadRecentActivity(), 500);
const textarea = document.getElementById("gemini-prompt");
if (textarea) {
textarea.addEventListener("input", function () {
this.style.height = "auto";
this.style.height = this.scrollHeight + "px";
});
}
});
function parseMarkdown(text) {
if (!text) return "";
let cleanText = text
.replace(/&/g, "&")
.replace(//g, ">")
.replace(/\*\*(.*?)\*\*/g, "$1")
.replace(/\*(.*?)\*/g, "$1");
const lines = cleanText.split("\n");
let formattedHTML = "";
lines.forEach((line) => {
line = line.trim();
if (!line) return;
if (/^(\d+\.|[A-Z]).*?:$/.test(line) || line.startsWith("###")) {
let content = line.replace(/^###\s*/, "");
formattedHTML += `${content}
`;
}
else if (/^[\-\*]\s+/.test(line)) {
let content = line.replace(/^[\-\*]\s+/, "");
formattedHTML += `
●
${content}
`;
}
else {
formattedHTML += `${line}
`;
}
});
return formattedHTML;
}
function escapeHtml(str) {
if (!str) return "";
const div = document.createElement("div");
div.textContent = str;
return div.innerHTML;
}
function appendUserMessage(text) {
const c = document.getElementById("chat-container");
c.insertAdjacentHTML(
"beforeend",
`
`
);
c.scrollTop = c.scrollHeight;
}
function appendGeminiMessage(text) {
const c = document.getElementById("chat-container");
const formattedHtml = parseMarkdown(text);
c.insertAdjacentHTML(
"beforeend",
`
`
);
c.scrollTop = c.scrollHeight;
}
function appendGeminiError(text) {
const c = document.getElementById("chat-container");
c.insertAdjacentHTML(
"beforeend",
`
`
);
c.scrollTop = c.scrollHeight;
}
function appendGeminiTyping() {
const c = document.getElementById("chat-container");
const id = "gemini-typing";
if (document.getElementById(id)) return;
c.insertAdjacentHTML(
"beforeend",
`
`
);
c.scrollTop = c.scrollHeight;
}
function removeGeminiTyping() {
document.getElementById("gemini-typing")?.remove();
}
async function handleChatSubmit() {
const input = document.getElementById("gemini-prompt");
const message = input.value.trim();
if (!message) return;
appendUserMessage(message);
input.value = "";
input.style.height = "52px";
appendGeminiTyping();
try {
const res = await fetch("./core/actions/user_gemini.php", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ prompt: message }),
});
removeGeminiTyping();
const data = await res.json();
if (!res.ok || data.error) {
appendGeminiError(data.error || "Gemini failed to respond.");
return;
}
appendGeminiMessage(data.reply);
} catch (e) {
removeGeminiTyping();
appendGeminiError("Unable to connect to Gemini. Please try again later.");
}
}