PRMSChallenge / web /past_data.html
Vineela Gampa
cosmetic fixes
98fa945 unverified
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<title>Past Analyzes - CTRL + ALT + HEAL</title>
<script src="https://cdn.tailwindcss.com"></script>
<script src="stylesheet" href="style.css"></script>
<script src="script.js"></script>
</head>
<body class="bg-[#F7F8F9] min-h-screen">
<nav
class="fixed top-0 left-0 w-full z-50 backdrop-blur-md bg-white/20 border-b border-white/30 shadow-md"
>
<div class="flex justify-between items-center w-full px-6 py-4">
<a
href="index.html"
class="text-2xl font-bold text-black hover:text-[var(--tropical-indigo)] transition"
>
CTRL + ALT + HEAL
</a>
<ul class="hidden md:flex space-x-6 font-medium text-gray-800">
<li>
<a
href="profile.html"
class="block text-gray-800 hover:text-[var(--tropical-indigo)]"
>Profile</a
>
</li>
<li>
<a
href="analyzer.html"
class="block text-gray-800 hover:text-[var(--tropical-indigo)]"
>Analyzer</a
>
</li>
<li>
<a
href="past_data.html"
class="block text-gray-800 hover:text-[var(--tropical-indigo)]"
>Past Reports</a
>
</li>
<li id="authNavItem">
<a
href="login.html"
class="block text-gray-800 hover:text-[var(--tropical-indigo)]"
>Login</a
>
</li>
</ul>
</div>
</nav>
<main class="container mx-auto px-6 pt-24">
<h2 class="text-xl font-semibold mb-4 text-sky-700">
Your Past Analyzes
</h2>
<p id="auth-status" class="text-sm text-gray-500 mb-6">
Checking sign-in status…
</p>
<div id="recs-container" class="space-y-4">
<p class="text-sm text-gray-500"></p>
</div>
</main>
<script type="module">
import { initializeApp } from "https://www.gstatic.com/firebasejs/9.22.0/firebase-app.js";
import {
getAuth,
onAuthStateChanged,
signOut,
} from "https://www.gstatic.com/firebasejs/9.22.0/firebase-auth.js";
import {
getFirestore,
collection,
query,
orderBy,
getDocs,
} from "https://www.gstatic.com/firebasejs/9.22.0/firebase-firestore.js";
const firebaseConfig = {
apiKey: "AIzaSyAPhM_Ee7cLzyKHs5zyFy8g5ZOk9-pubRI",
authDomain: "login-tutorial-7a9e1.firebaseapp.com",
projectId: "login-tutorial-7a9e1",
storageBucket: "login-tutorial-7a9e1.firebasestorage.app",
messagingSenderId: "491093197824",
appId: "1:491093197824:web:9f86659034af7e6a8244e5",
measurementId: "G-JM7T9N6ZLZ",
};
const app = initializeApp(firebaseConfig);
const auth = getAuth(app);
const db = getFirestore(app);
let currentUser = null;
onAuthStateChanged(auth, (user) => {
const authNavItem = document.getElementById("authNavItem");
if (authNavItem) {
if (user) {
authNavItem.innerHTML =
'<button onclick="logout()" class="hover:text-[#6B9080] text-red-600">Logout</button>';
} else {
authNavItem.innerHTML =
'<a href="login.html" class="hover:text-[#6B9080]">Login</a>';
}
}
});
window.logout = async () => {
try {
await signOut(auth);
localStorage.clear();
window.location.href = "login.html";
} catch (error) {
console.error("Error signing out:", error);
}
};
const statusEl = document.getElementById("auth-status");
const recsEl = document.getElementById("recs-container");
function dbg(label, data) {
console.log(
`[PastReports][${new Date().toISOString()}] ${label}:`,
data
);
}
const SHOW_DEBUG = false;
let debugBannerEl = null;
if (SHOW_DEBUG) {
debugBannerEl = document.createElement("div");
debugBannerEl.style.cssText =
"font-size:12px;margin-top:6px;color:#6b7280;";
statusEl.after(debugBannerEl);
}
function setDebugBanner(msg) {
if (debugBannerEl) debugBannerEl.textContent = `Debug: ${msg}`;
}
window.toggleBox = function (id) {
const content = document.getElementById(`box-content-${id}`);
const arrow = document.getElementById(`box-arrow-${id}`);
if (!content || !arrow) return;
const open =
content.style.maxHeight && content.style.maxHeight !== "0px";
if (open) {
content.style.maxHeight = "0px";
arrow.style.transform = "rotate(0deg)";
} else {
content.style.maxHeight = content.scrollHeight + "px";
arrow.textContent = "Close Full View";
}
};
function safeJson(x) {
try {
return typeof x === "string" ? JSON.parse(x) : x;
} catch {
return x;
}
}
function topAnomalyLabels(anoms, n = 3) {
const arr = Array.isArray(anoms) ? anoms : [];
return arr
.slice(0, n)
.map((a) =>
(a?.findings ?? a?.condition ?? a?.measurement ?? "").toString()
)
.filter(Boolean);
}
function deriveReportName(row) {
if (row.filename) return row.filename;
if (row.report_date)
return `Report ${new Date(row.report_date).toLocaleDateString()}`;
if (row.ocr_text) {
const first = row.ocr_text.split(/\r?\n/).find((l) => l.trim());
if (first) return first.slice(0, 50) + (first.length > 50 ? "…" : "");
}
return "Report";
}
function renderBackendReportCollapsible(row, idx) {
const id = `r${row.id || idx}`;
const created = row.created_at
? new Date(row.created_at).toLocaleString()
: "";
const reportName = deriveReportName(row);
const anomaliesRaw = safeJson(row.anomalies);
const anomalies = Array.isArray(anomaliesRaw) ? anomaliesRaw : [];
const top3 = topAnomalyLabels(anomalies, 3);
return `
<div class="bg-white border border-gray-200 rounded-lg shadow">
<!-- Header (clickable) -->
<button
class="w-full text-left p-4 flex items-start justify-between gap-3 hover:bg-gray-50 rounded-t-lg"
onclick="toggleBox('${id}')"
>
<div class="min-w-0">
<div class="text-xs text-gray-500">${created || "—"}</div>
<div class="font-semibold text-sky-700 truncate">${reportName}</div>
${
top3.length
? `<div class="flex flex-wrap gap-2 mt-2">
${top3
.map(
(t) => `
<span class="text-xs bg-gray-100 border border-gray-200 px-2 py-1 rounded">
${t}
</span>
`
)
.join("")}
</div>`
: `<div class="text-xs text-gray-400 mt-2">No anomalies detected</div>`
}
</div>
<span id="box-arrow-${id}" class="text-gray-500 transition-transform select-none mt-1">Open Full View</span>
</button>
<!-- Collapsible body -->
<div
id="box-content-${id}"
class="overflow-hidden transition-all duration-300 max-h-0 border-t border-gray-200"
>
<div class="p-4 space-y-3">
<!-- Full anomalies list -->
${
anomalies.length
? `
<div class="pt-2 border-t border-gray-100">
<div class="font-medium mb-2">Findings</div>
${anomalies
.map(
(a, i) => `
<div class="border-t border-gray-200 pt-2 mt-2">
<div class="font-medium">Finding ${i + 1}: ${
a?.findings ?? ""
}</div>
<div class="text-sm text-gray-600">Severity: ${
a?.severity ?? ""
}</div>
<div class="text-sm text-gray-600">Recommendations: ${(Array.isArray(
a?.recommendations
)
? a.recommendations
: []
).join(", ")}</div>
</div>
`
)
.join("")}
</div>
`
: ""
}
</div>
</div>
</div>
`;
}
function renderFirestoreReportCollapsible(item, idx) {
const id = `f${idx}`;
const created =
item?.createdAt && item.createdAt.toDate
? item.createdAt.toDate().toLocaleString()
: "";
const reportName = item?.reportDate
? `Report ${item.reportDate}`
: "Report";
const anomalies = Array.isArray(item?.resolutions)
? item.resolutions
: [];
const top3 = topAnomalyLabels(anomalies, 3);
return `
<div class="bg-white border border-gray-200 rounded-lg shadow">
<!-- Header (clickable) -->
<button
class="w-full text-left p-4 flex items-start justify-between gap-3 hover:bg-gray-50 rounded-t-lg"
onclick="toggleBox('${id}')"
>
<div class="min-w-0">
<div class="text-xs text-gray-500">${created || "—"}</div>
<div class="font-semibold text-blue-700 truncate">${reportName}</div>
${
top3.length
? `<div class="flex flex-wrap gap-2 mt-2">
${top3
.map(
(t) => `
<span class="text-xs bg-gray-100 border border-gray-200 px-2 py-1 rounded">
${t}
</span>`
)
.join("")}
</div>`
: `<div class="text-xs text-gray-400 mt-2">No anomalies detected</div>`
}
</div>
<span id="box-arrow-${id}" class="text-gray-500 transition-transform select-none mt-1">Open Full View</span>
</button>
<!-- Collapsible body -->
<div
id="box-content-${id}"
class="overflow-hidden transition-all duration-300 max-h-0 border-t border-gray-200"
>
<div class="p-4 space-y-3">
<!-- OCR -->
<!-- Full anomalies list -->
${
anomalies.length
? `
<div class="pt-2 border-t border-gray-100">
<div class="font-medium mb-2">Findings</div>
${anomalies
.map(
(a, i) => `
<div class="border-t border-gray-200 pt-2 mt-2">
<div class="font-medium">Finding ${i + 1}: ${
a?.findings ?? ""
}</div>
<div class="text-sm text-gray-600">Severity: ${
a?.severity ?? ""
}</div>
<div class="text-sm text-gray-600">Recommendations: ${
Array.isArray(a?.recommendations)
? a.recommendations.join(", ")
: ""
}</div>
</div>`
)
.join("")}
</div>
`
: ""
}
</div>
</div>
</div>
`;
}
function renderBackendRow(row) {
const created = row.created_at
? new Date(row.created_at).toLocaleString()
: "";
const reportDate = row.report_date || "N/A";
const anomalies = (() => {
try {
return Array.isArray(row.anomalies)
? row.anomalies
: JSON.parse(row.anomalies || "[]");
} catch {
return [];
}
})();
return `
<div class="bg-white border border-gray-200 rounded-lg p-4 shadow">
<div class="flex justify-between mb-2">
<div class="font-semibold text-blue-700">Report: ${reportDate}</div>
<div class="text-xs text-gray-500">${created}</div>
</div>
<pre class="whitespace-pre-wrap text-sm text-gray-700 mb-2">${
row.ocr_text || ""
}</pre>
${anomalies
.map(
(r, i) => `
<div class="border-t border-gray-200 pt-2 mt-2">
<div class="font-medium">Finding ${i + 1}: ${
r?.findings ?? ""
}</div>
<div class="text-sm text-gray-600">Severity: ${
r?.severity ?? ""
}</div>
<div class="text-sm text-gray-600">Recommendations: ${(Array.isArray(
r?.recommendations
)
? r.recommendations
: []
).join(", ")}</div>
</div>
`
)
.join("")}
</div>`;
}
function renderList(htmlItems) {
recsEl.innerHTML =
htmlItems && htmlItems.length
? htmlItems.join("")
: '<p class="text-sm text-gray-500">No saved analyses yet.</p>';
}
async function loadFromBackend(user) {
try {
const userId = user.email || user.uid;
const url = api('reports/', { user_id: userId });
dbg("Backend URL", url);
const res = await fetch(url, { method: "GET" });
dbg("Backend HTTP status", res.status);
if (!res.ok) throw new Error(`HTTP ${res.status}`);
const data = await res.json();
dbg("Backend payload", data);
return Array.isArray(data) ? data : [];
} catch (e) {
console.error("Backend fetch failed:", e);
dbg("Backend fetch error", e.message || e);
return [];
}
}
async function loadFromFirestore(user) {
try {
const q = query(
collection(db, "users", user.uid, "analyses"),
orderBy("createdAt", "desc")
);
dbg("Firestore query uid", user.uid);
const snap = await getDocs(q);
const rows = snap.docs.map((d) => d.data());
dbg("Firestore count", rows.length);
return rows;
} catch (e) {
console.error("Firestore fetch failed:", e);
dbg("Firestore fetch error", e.message || e);
return [];
}
}
onAuthStateChanged(auth, async (user) => {
if (!user) {
statusEl.textContent = "Not signed in.";
renderList([]);
setDebugBanner("User is not signed in.");
return;
}
statusEl.textContent = `Signed in as ${user.email || user.uid}`;
const backendRows = await loadFromBackend(user);
const firestoreRows = await loadFromFirestore(user);
setDebugBanner(
`backend: ${backendRows.length} | firestore: ${firestoreRows.length}`
);
dbg("Summary", {
backend: backendRows.length,
firestore: firestoreRows.length,
});
if (backendRows.length > 0) {
recsEl.innerHTML = backendRows
.map((row, i) => renderBackendReportCollapsible(row, i))
.join("");
} else if (firestoreRows.length > 0) {
recsEl.innerHTML = firestoreRows
.map((row, i) => renderFirestoreReportCollapsible(row, i))
.join("");
} else {
recsEl.innerHTML =
'<p class="text-sm text-gray-500">No saved analyses yet.</p>';
}
});
</script>
</body>
</html>