PRMSChallengeOct / web /past_data.html
vineelagampa's picture
Update web/past_data.html (#46)
d7f85fc verified
<!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="bg-white border border-gray-200 px-6 py-4 mb-8">
<div class="container mx-auto flex justify-between items-center">
<a href="index.html" class="text-2xl font-bold text-[#6B9080]">CTRL + ALT + HEAL</a>
<ul class="flex space-x-6 text-sm font-medium text-gray-700">
<li><a href="index.html" class="hover:text-[#6B9080]">Home</a></li>
<li><a href="analyzer.html" class="hover:text-[#6B9080]">Analyzer</a></li>
<li><a href="past_data.html" class="hover:text-[#6B9080]">Past Reports</a></li>
<li><a href="profile.html" class="hover:text-[#6B9080]">Profile</a></li>
<li id="authNavItem"><a href="login.html" class="hover:text-[#6B9080]">Login</a></li>
</ul>
</div>
</nav>
<main class="max-w-4xl mx-auto px-4">
<h1 class="text-2xl font-bold text-green-700 mb-4">Your Past Analyzes</h1>
<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 = true;
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-green-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 -->
<div>
<div class="font-medium mb-1">OCR Text</div>
<pre class="whitespace-pre-wrap text-sm text-gray-700 bg-gray-50 border rounded p-3">${row.ocr_text || ""}</pre>
</div>
<!-- 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-green-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 -->
<div>
<div class="font-medium mb-1">OCR Text</div>
<pre class="whitespace-pre-wrap text-sm text-gray-700 bg-gray-50 border rounded p-3">${
item?.ocr_text || ""
}</pre>
</div>
<!-- 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-green-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>