vineelagampa meamgodyay commited on
Commit
2848fd0
·
verified ·
1 Parent(s): 8dc4c2c

Update web/past_data.html (#33)

Browse files

- Update web/past_data.html (1a2b2c6ea304d4d4958e94c4b0b2bf279bead957)


Co-authored-by: Vihaan Jyothiswaroop <meamgodyay@users.noreply.huggingface.co>

Files changed (1) hide show
  1. web/past_data.html +335 -428
web/past_data.html CHANGED
@@ -1,448 +1,355 @@
1
  <!DOCTYPE html>
2
  <html lang="en">
3
- <head>
4
- <meta charset="UTF-8" />
5
- <title>Past Analyzes - CTRL + ALT + HEAL</title>
6
- <script src="https://cdn.tailwindcss.com"></script>
7
- <script src="script.js"></script>
8
- <link rel="stylesheet" href="style.css" />
9
- </head>
10
- <body class="bg-[#F7F8F9] min-h-screen">
11
- <nav
12
- class="fixed top-0 left-0 w-full z-50 backdrop-blur-md bg-white/20 border-b border-white/30 shadow-md"
13
- >
14
- <div class="flex justify-between items-center w-full px-6 py-4">
15
- <!-- Logo -->
16
- <a
17
- href="index.html"
18
- class="text-2xl font-bold text-black hover:text-[var(--tropical-indigo)] transition"
19
- >
20
- CTRL + ALT + HEAL
21
- </a>
22
-
23
- <ul class="hidden md:flex space-x-6 font-medium text-gray-800">
24
- <li><a href="index.html" class="nav-link">Home</a></li>
25
- <li><a href="profile.html" class="nav-link">Profile</a></li>
26
- <li><a href="analyzer.html" class="nav-link">Analyzer</a></li>
27
- <li><a href="past_data.html" class="nav-link">Past Reports</a></li>
28
- <li id="authNavItem">
29
- <a href="login.html" class="nav-link">Login</a>
30
- </li>
31
- </ul>
32
-
33
- <!-- Hamburger Menu -->
34
- <button
35
- id="hamburger"
36
- class="md:hidden text-[var(--latte-cream)] text-2xl"
37
- >
38
-
39
- </button>
40
- </div>
41
-
42
- <!-- Mobile Menu -->
43
- <ul
44
- id="mobile-menu"
45
- class="hidden flex-col space-y-4 bg-white/30 backdrop-blur-lg border border-white/20 rounded-xl shadow-lg mt-2 p-4 mx-6 md:hidden"
46
- >
47
- <li>
48
- <a
49
- href="index.html"
50
- class="block text-gray-800 hover:text-[var(--tropical-indigo)]"
51
- >Home</a
52
- >
53
- </li>
54
- <li>
55
- <a
56
- href="analyzer.html"
57
- class="block text-gray-800 hover:text-[var(--tropical-indigo)]"
58
- >Analyzer</a
59
- >
60
- </li>
61
- <li>
62
- <a
63
- href="past_data.html"
64
- class="block text-gray-800 hover:text-[var(--tropical-indigo)]"
65
- >Past Reports</a
66
- >
67
- </li>
68
- <li>
69
- <a
70
- href="profile.html"
71
- class="block text-gray-800 hover:text-[var(--tropical-indigo)]"
72
- >Profile</a
73
- >
74
- </li>
75
- <li>
76
- <a
77
- href="login.html"
78
- class="block text-gray-800 hover:text-[var(--tropical-indigo)]"
79
- >Login</a
80
- >
81
- </li>
82
  </ul>
83
- </nav>
84
-
85
- <main class="max-w-4xl mx-auto px-4 pt-24 text-gray-800">
86
- <div class="bg-white border border-gray-200 rounded-lg p-6 shadow mb-6">
87
- <h2 class="text-xl font-semibold mb-4">
88
- Your Past Analyzes
89
- </h1>
90
- <p id="auth-status" class="text-sm text-gray-500 mb-6">
91
- Checking sign-in status…
92
- </p>
93
-
94
- <div id="recs-container" class="space-y-4">
95
- <p class="text-sm text-gray-500">
96
- Sign in to view your saved analyzes.
97
- </p>
98
- </div>
99
- </div>
100
- </main>
101
-
102
- <!-- Floating Chat Button -->
103
- <button
104
- id="chat-toggle"
105
- class="fixed bottom-6 right-6 bg-[var(--tropical-indigo)] text-white px-5 py-3 rounded-full shadow-lg hover:scale-105 transition"
106
- >
107
- 💬 Chat
108
- </button>
109
-
110
- <!-- Chatbot Drawer -->
111
- <div
112
- id="chat-drawer"
113
- class="fixed top-16 right-0 w-96 max-w-full h-[calc(100%-4rem)] bg-white shadow-lg border-l border-gray-200 transform translate-x-full transition-transform duration-300 ease-in-out z-40 flex flex-col"
114
- >
115
- <div class="flex justify-between items-center p-4 border-b">
116
- <h3 class="text-lg font-semibold">Ask Chatbot</h3>
117
- <button id="chat-close" class="text-gray-600 hover:text-black text-xl">
118
-
119
- </button>
120
- </div>
121
- <div id="chat-output" class="flex-1 overflow-auto p-4 space-y-2 text-sm">
122
- <p><strong>Chatbot:</strong> Ask me something about your report</p>
123
- </div>
124
- <div class="flex gap-2 p-4 border-t">
125
- <input
126
- type="text"
127
- id="user-question"
128
- placeholder="Ask a question..."
129
- class="flex-1 rounded px-3 py-2 focus:outline-none border"
130
- />
131
- <button id="ask-btn" class="btn-primary px-4 py-2 rounded">Ask</button>
132
- </div>
133
  </div>
 
134
 
135
- <script>
136
- const chatDrawer = document.getElementById("chat-drawer");
137
- const chatToggle = document.getElementById("chat-toggle");
138
- const chatClose = document.getElementById("chat-close");
139
- chatToggle.addEventListener("click", () =>
140
- chatDrawer.classList.toggle("translate-x-full")
141
- );
142
- chatClose.addEventListener("click", () =>
143
- chatDrawer.classList.add("translate-x-full")
144
- );
145
- </script>
146
-
147
- <script type="module">
148
- import {
149
- getFirestore,
150
- collection,
151
- query,
152
- orderBy,
153
- getDocs,
154
- } from "https://www.gstatic.com/firebasejs/9.22.0/firebase-firestore.js";
155
- import { initializeApp } from "https://www.gstatic.com/firebasejs/9.22.0/firebase-app.js";
156
- import {
157
- getAuth,
158
- onAuthStateChanged,
159
- signOut,
160
- } from "https://www.gstatic.com/firebasejs/9.22.0/firebase-auth.js";
161
-
162
- const firebaseConfig = {
163
- apiKey: "AIzaSyAKWstCc7HqMgV8DmH2Sy6QwUP3JvZxu9g",
164
- authDomain: "appchallenge-c8fe3.firebaseapp.com",
165
- projectId: "appchallenge-c8fe3",
166
- storageBucket: "appchallenge-c8fe3.firebasestorage.app",
167
- messagingSenderId: "943791692455",
168
- appId: "1:943791692455:web:ae04d06bf09669b6bdd982",
169
- measurementId: "G-HJ7EHW8V4N",
170
- };
171
-
172
- const app = initializeApp(firebaseConfig);
173
- const auth = getAuth(app);
174
- const db = getFirestore(app);
175
- window.currentUser = null;
176
- window.chatContext = null;
177
- window.setChatContextFromId = function (id) {
178
- const pre = document.querySelector(`#content-${id} .ocr-pre`);
179
- window.chatContext = pre ? pre.textContent.trim() : null;
180
- const chatOutput = document.getElementById("chat-output");
181
- if (chatOutput) {
182
- chatOutput.innerHTML += `<p class="text-xs text-gray-500"><em>Context set from selected report.</em></p>`;
183
- chatOutput.scrollTop = chatOutput.scrollHeight;
184
- }
185
- };
186
- onAuthStateChanged(auth, (user) => {
187
- const authNavItem = document.getElementById("authNavItem");
188
- if (user) {
189
- authNavItem.innerHTML =
190
- '<button onclick="logout()" class="hover:text-[#6B9080] text-red-600">Logout</button>';
191
- } else {
192
- authNavItem.innerHTML =
193
- '<a href="login.html" class="hover:text-[#6B9080]">Login</a>';
194
- }
195
- });
196
-
197
- window.logout = async () => {
198
- try {
199
- await signOut(auth);
200
- localStorage.clear();
201
- window.location.href = "login.html";
202
- } catch (error) {
203
- console.error("Error signing out:", error);
204
- }
205
- };
206
-
207
- const statusEl = document.getElementById("auth-status");
208
- const recsEl = document.getElementById("recs-container");
209
- async function sendReportToBackend(report, userId) {
210
- try {
211
- const response = await fetch("http://localhost:9000/save_report/", {
212
- method: "POST",
213
- headers: {
214
- "Content-Type": "application/json",
215
- },
216
- body: JSON.stringify({
217
- ...report,
218
- user_id: userId,
219
- sync_timestamp: new Date().toISOString(),
220
- }),
221
- });
222
-
223
- if (!response.ok) {
224
- console.warn(
225
- `Failed to sync report to backend: ${response.status}`
226
- );
227
- } else {
228
- console.log("Report successfully synced to backend");
229
- }
230
- } catch (error) {
231
- console.warn(
232
- "Error syncing report to backend (continuing anyway):",
233
- error
234
- );
235
- }
236
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
237
 
238
- function renderAnalysisWithCustomToggle(item) {
239
- const d =
240
- item.createdAt && item.createdAt.toDate
241
- ? item.createdAt.toDate().toLocaleString()
242
- : "";
243
- const uniqueId = `ocr-${Date.now()}-${Math.random()
244
- .toString(36)
245
- .substr(2, 9)}`; // Generate unique ID
 
 
 
246
 
247
- return `
248
- <div class="bg-white border border-gray-200 rounded-lg p-4 shadow">
249
- <div class="flex justify-between mb-2">
250
- <div class="text-xs text-gray-500">${d}</div>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
251
  </div>
252
-
253
- <!-- Collapsible OCR Text Section with Custom Toggle -->
254
- <div class="mb-4">
255
- <div class="font-semibold text-green-700 cursor-pointer hover:text-green-800 py-2 flex justify-between items-center"
256
- onclick="toggleOCR('${uniqueId}')">
257
- <span>Report ${item.reportDate || "N/A"}</span>
258
- <span class="arrow transition-transform duration-300" id="arrow-${uniqueId}">▶</span>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
259
  </div>
260
- <div class="ocr-content overflow-hidden transition-all duration-300 max-h-0" id="content-${uniqueId}">
261
- <div class="mt-2 p-3 bg-gray-50 rounded border">
262
- <pre class="whitespace-pre-wrap text-sm text-gray-700">${
263
- item.ocr_text || ""
 
 
 
 
 
 
 
 
 
 
264
  }</pre>
265
  </div>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
266
  </div>
267
  </div>
268
-
269
- <!-- Findings Section -->
270
- ${(Array.isArray(item.Detected_Anomolies)
271
- ? item.Detected_Anomolies
272
- : []
273
- )
274
- .map(
275
- (r, i) => `
276
- <div class="border-t border-gray-200 pt-2 mt-2">
277
- <div class="font-medium">Finding ${i + 1}: ${
278
- r.findings || ""
279
- }</div>
280
- <div class="text-sm text-gray-600">Severity: ${
281
- r.severity || ""
282
- }</div>
283
- <div class="text-sm text-gray-600">Recommendations: ${(
284
- r.recommendations || []
285
- ).join(", ")}</div>
286
- </div>
287
- `
288
- )
289
- .join("")}
290
  </div>
291
  `;
292
- }
 
 
 
 
 
 
 
 
 
293
 
294
- function toggleOCR(id) {
295
- const content = document.getElementById(`content-${id}`);
296
- const arrow = document.getElementById(`arrow-${id}`);
297
-
298
- if (content.style.maxHeight && content.style.maxHeight !== "0px") {
299
- content.style.maxHeight = "0px";
300
- arrow.style.transform = "rotate(0deg)";
301
- } else {
302
- content.style.maxHeight = content.scrollHeight + "px";
303
- arrow.style.transform = "rotate(90deg)";
304
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
305
  }
306
 
307
- onAuthStateChanged(auth, async (user) => {
308
- if (user) {
309
- statusEl.textContent = `Signed in as ${user.email || user.uid}`;
310
-
311
- async function getPastReports() {
312
- try {
313
- const url = api("reports/", { user_id: user.email });
314
- const response = await fetch(url, {
315
- method: "GET",
316
- headers: {
317
- "Content-Type": "application/json",
318
- },
319
- });
320
- if (!response.ok) {
321
- throw new Error(`HTTP error! status: ${response.status}`);
322
- }
323
- const data = await response.json();
324
- console.log("Report successfully sent to backend:", data);
325
- recsEl.innerHTML = data
326
- .map((doc) => renderAnalysisWithCustomToggle(doc))
327
- .join("");
328
- } catch (error) {
329
- console.error("Error sending report to backend:", error);
330
- recsEl.innerHTML =
331
- '<p class="text-sm text-gray-500">No saved analyses yet.</p>';
332
- }
333
- }
334
- getPastReports();
335
-
336
- /* try {
337
- const q = query(
338
- collection(db, "users", user.uid, "analyses"),
339
- orderBy("createdAt", "desc")
340
- );
341
- const snap = await getDocs(q);
342
-
343
- if (snap.empty) {
344
- recsEl.innerHTML = '<p class="text-sm text-gray-500">No saved analyses yet.</p>';
345
- } else {
346
- const analyses = [];
347
-
348
- snap.forEach(doc => {
349
- const data = { id: doc.id, ...doc.data() };
350
- analyses.push(data);
351
-
352
- sendReportToBackend(data, user.uid);
353
- });
354
-
355
- recsEl.innerHTML = analyses.map(doc => renderAnalysis(doc)).join("");
356
-
357
- const syncStatus = document.createElement('p');
358
- syncStatus.className = 'text-xs text-blue-500 mt-2';
359
- syncStatus.textContent = `Syncing ${analyses.length} reports to backend...`;
360
- recsEl.appendChild(syncStatus);
361
-
362
- setTimeout(() => {
363
- if (syncStatus.parentNode) {
364
- syncStatus.remove();
365
- }
366
- }, 3000);
367
- }
368
- } catch (error) {
369
- console.error('Error fetching analyses:', error);
370
- recsEl.innerHTML = '<p class="text-sm text-red-500">Error loading analyses.</p>';
371
- }*/
372
- } else {
373
- statusEl.textContent = "Not signed in.";
374
- recsEl.innerHTML =
375
- '<p class="text-sm text-gray-500">Please sign in to see your analyses.</p>';
376
- }
377
- document.getElementById("ask-btn").onclick = async () => {
378
- const q = document.getElementById("user-question").value.trim();
379
- if (!q) return;
380
-
381
- // require a selected report as context
382
- if (!window.chatContext) {
383
- alert(
384
- "Please click 'Chat about this report' on a saved report first to set context."
385
- );
386
- return;
387
- }
388
-
389
- document.getElementById("ask-btn").onclick = async () => {
390
- const q = document.getElementById("user-question").value.trim();
391
- if (!q) return;
392
-
393
- // require a selected report as context
394
- if (!window.chatContext) {
395
- alert(
396
- "Please click 'Chat about this report' on a saved report first to set context."
397
- );
398
- return;
399
- }
400
-
401
- const chat = document.getElementById("chat-output");
402
- chat.innerHTML += `<p><strong>You:</strong> ${q}</p>`;
403
- chat.scrollTop = chat.scrollHeight;
404
-
405
- chat.innerHTML += `<p><strong>Chatbot:</strong> <em>Thinking...</em></p>`;
406
- chat.scrollTop = chat.scrollHeight;
407
-
408
- try {
409
- const response = await fetch(api("chat/"), {
410
- method: "POST",
411
- headers: {
412
- "Content-Type": "application/json",
413
- },
414
- body: JSON.stringify({
415
- question: q,
416
- user_id:
417
- auth && auth.currentUser
418
- ? auth.currentUser.uid || auth.currentUser.email
419
- : "anonymous",
420
- context: window.chatContext,
421
- }),
422
- });
423
-
424
- if (!response.ok) {
425
- throw new Error(`HTTP error! status: ${response.status}`);
426
- }
427
 
428
- const data = await response.json();
429
-
430
- //now addign acctual reposnse
431
- const messages = chat.querySelectorAll("p");
432
- const lastMessage = messages[messages.length - 1];
433
- lastMessage.innerHTML = `<strong>Chatbot:</strong> ${data.answer}`;
434
- } catch (error) {
435
- console.error("Error:", error);
436
- const messages = chat.querySelectorAll("p");
437
- const lastMessage = messages[messages.length - 1];
438
- lastMessage.innerHTML = `<strong>Chatbot:</strong> Sorry, I encountered an error: ${error.message}`;
439
- }
440
-
441
- document.getElementById("user-question").value = "";
442
- chat.scrollTop = chat.scrollHeight;
443
- };
444
- };
445
- });
446
- </script>
447
- </body>
448
- </html>
 
1
  <!DOCTYPE html>
2
  <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <title>Past Analyzes - CTRL + ALT + HEAL</title>
6
+ <script src="https://cdn.tailwindcss.com"></script>
7
+ <script src="script.js"></script>
8
+ </head>
9
+ <body class="bg-[#F7F8F9] min-h-screen">
10
+ <nav class="bg-white border border-gray-200 px-6 py-4 mb-8">
11
+ <div class="container mx-auto flex justify-between items-center">
12
+ <a href="index.html" class="text-2xl font-bold text-[#6B9080]">CTRL + ALT + HEAL</a>
13
+ <ul class="flex space-x-6 text-sm font-medium text-gray-700">
14
+ <li><a href="index.html" class="hover:text-[#6B9080]">Home</a></li>
15
+ <li><a href="analyzer.html" class="hover:text-[#6B9080]">Analyzer</a></li>
16
+ <li><a href="past_data.html" class="hover:text-[#6B9080]">Past Reports</a></li>
17
+ <li><a href="profile.html" class="hover:text-[#6B9080]">Profile</a></li>
18
+ <li id="authNavItem"><a href="login.html" class="hover:text-[#6B9080]">Login</a></li>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
19
  </ul>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
20
  </div>
21
+ </nav>
22
 
23
+ <main class="max-w-4xl mx-auto px-4">
24
+ <h1 class="text-2xl font-bold text-green-700 mb-4">Your Past Analyzes</h1>
25
+ <p id="auth-status" class="text-sm text-gray-500 mb-6">Checking sign-in status…</p>
26
+
27
+ <div id="recs-container" class="space-y-4">
28
+ <p class="text-sm text-gray-500"></p>
29
+ </div>
30
+ </main>
31
+
32
+
33
+
34
+
35
+ <script type="module">
36
+ import { initializeApp } from 'https://www.gstatic.com/firebasejs/9.22.0/firebase-app.js';
37
+ import { getAuth, onAuthStateChanged, signOut } from 'https://www.gstatic.com/firebasejs/9.22.0/firebase-auth.js';
38
+ import { getFirestore, collection, query, orderBy, getDocs } from 'https://www.gstatic.com/firebasejs/9.22.0/firebase-firestore.js';
39
+
40
+ const firebaseConfig = {
41
+ apiKey: "AIzaSyAPhM_Ee7cLzyKHs5zyFy8g5ZOk9-pubRI",
42
+ authDomain: "login-tutorial-7a9e1.firebaseapp.com",
43
+ projectId: "login-tutorial-7a9e1",
44
+ storageBucket: "login-tutorial-7a9e1.firebasestorage.app",
45
+ messagingSenderId: "491093197824",
46
+ appId: "1:491093197824:web:9f86659034af7e6a8244e5",
47
+ measurementId: "G-JM7T9N6ZLZ"
48
+ };
49
+ const app = initializeApp(firebaseConfig);
50
+ const auth = getAuth(app);
51
+ const db = getFirestore(app);
52
+
53
+
54
+ const statusEl = document.getElementById("auth-status");
55
+ const recsEl = document.getElementById("recs-container");
56
+
57
+ function dbg(label, data) {
58
+ console.log(`[PastReports][${new Date().toISOString()}] ${label}:`, data);
59
+ }
60
+ const SHOW_DEBUG = true;
61
+ let debugBannerEl = null;
62
+ if (SHOW_DEBUG) {
63
+ debugBannerEl = document.createElement('div');
64
+ debugBannerEl.style.cssText = 'font-size:12px;margin-top:6px;color:#6b7280;';
65
+ statusEl.after(debugBannerEl);
66
+ }
67
+ function setDebugBanner(msg) {
68
+ if (debugBannerEl) debugBannerEl.textContent = `Debug: ${msg}`;
69
+ }
70
+
71
+ window.logout = async () => {
72
+ try { await signOut(auth); localStorage.clear(); location.href = 'login.html'; }
73
+ catch (e) { console.error('Error signing out:', e); }
74
+ };
75
+
76
+ window.toggleBox = function (id) {
77
+ const content = document.getElementById(`box-content-${id}`);
78
+ const arrow = document.getElementById(`box-arrow-${id}`);
79
+ if (!content || !arrow) return;
80
+
81
+ const open = content.style.maxHeight && content.style.maxHeight !== "0px";
82
+ if (open) {
83
+ content.style.maxHeight = "0px";
84
+ arrow.style.transform = "rotate(0deg)";
85
+ } else {
86
+ content.style.maxHeight = content.scrollHeight + "px";
87
+ arrow.textContent = "Close Full View";
88
+ }
89
+ };
90
+
91
+ function safeJson(x) {
92
+ try { return typeof x === "string" ? JSON.parse(x) : x; } catch { return x; }
93
+ }
94
+ function topAnomalyLabels(anoms, n = 3) {
95
+ const arr = Array.isArray(anoms) ? anoms : [];
96
+ return arr.slice(0, n).map(a =>
97
+ (a?.findings ?? a?.condition ?? a?.measurement ?? "").toString()
98
+ ).filter(Boolean);
99
+ }
100
+ function deriveReportName(row) {
101
+ if (row.filename) return row.filename;
102
+ if (row.report_date) return `Report ${new Date(row.report_date).toLocaleDateString()}`;
103
+ if (row.ocr_text) {
104
+ const first = row.ocr_text.split(/\r?\n/).find(l => l.trim());
105
+ if (first) return first.slice(0, 50) + (first.length > 50 ? "…" : "");
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
106
  }
107
+ return "Report";
108
+ }
109
+
110
+ function renderBackendReportCollapsible(row, idx) {
111
+ const id = `r${row.id || idx}`;
112
+ const created = row.created_at ? new Date(row.created_at).toLocaleString() : "";
113
+ const reportName = deriveReportName(row);
114
+
115
+ const anomaliesRaw = safeJson(row.anomalies);
116
+ const anomalies = Array.isArray(anomaliesRaw) ? anomaliesRaw : [];
117
+ const top3 = topAnomalyLabels(anomalies, 3);
118
+
119
+ return `
120
+ <div class="bg-white border border-gray-200 rounded-lg shadow">
121
+ <!-- Header (clickable) -->
122
+ <button
123
+ class="w-full text-left p-4 flex items-start justify-between gap-3 hover:bg-gray-50 rounded-t-lg"
124
+ onclick="toggleBox('${id}')"
125
+ >
126
+ <div class="min-w-0">
127
+ <div class="text-xs text-gray-500">${created || "—"}</div>
128
+ <div class="font-semibold text-green-700 truncate">${reportName}</div>
129
+ ${
130
+ top3.length
131
+ ? `<div class="flex flex-wrap gap-2 mt-2">
132
+ ${top3.map(t => `
133
+ <span class="text-xs bg-gray-100 border border-gray-200 px-2 py-1 rounded">
134
+ ${t}
135
+ </span>
136
+ `).join("")}
137
+ </div>`
138
+ : `<div class="text-xs text-gray-400 mt-2">No anomalies detected</div>`
139
+ }
140
+ </div>
141
+ <span id="box-arrow-${id}" class="text-gray-500 transition-transform select-none mt-1">Open Full View</span>
142
+ </button>
143
 
144
+ <!-- Collapsible body -->
145
+ <div
146
+ id="box-content-${id}"
147
+ class="overflow-hidden transition-all duration-300 max-h-0 border-t border-gray-200"
148
+ >
149
+ <div class="p-4 space-y-3">
150
+ <!-- OCR -->
151
+ <div>
152
+ <div class="font-medium mb-1">OCR Text</div>
153
+ <pre class="whitespace-pre-wrap text-sm text-gray-700 bg-gray-50 border rounded p-3">${row.ocr_text || ""}</pre>
154
+ </div>
155
 
156
+ <!-- Full anomalies list -->
157
+ ${
158
+ anomalies.length
159
+ ? `
160
+ <div class="pt-2 border-t border-gray-100">
161
+ <div class="font-medium mb-2">Findings</div>
162
+ ${anomalies.map((a, i) => `
163
+ <div class="border-t border-gray-200 pt-2 mt-2">
164
+ <div class="font-medium">Finding ${i + 1}: ${a?.findings ?? ""}</div>
165
+ <div class="text-sm text-gray-600">Severity: ${a?.severity ?? ""}</div>
166
+ <div class="text-sm text-gray-600">Recommendations: ${(Array.isArray(a?.recommendations) ? a.recommendations : []).join(", ")}</div>
167
+ </div>
168
+ `).join("")}
169
+ </div>
170
+ `
171
+ : ""
172
+ }
173
+ </div>
174
  </div>
175
+ </div>
176
+ `;
177
+ }
178
+
179
+ function renderFirestoreReportCollapsible(item, idx) {
180
+ const id = `f${idx}`;
181
+ const created =
182
+ item?.createdAt && item.createdAt.toDate
183
+ ? item.createdAt.toDate().toLocaleString()
184
+ : "";
185
+ const reportName = item?.reportDate
186
+ ? `Report ${item.reportDate}`
187
+ : "Report";
188
+ const anomalies = Array.isArray(item?.resolutions) ? item.resolutions : [];
189
+
190
+ const top3 = topAnomalyLabels(anomalies, 3);
191
+
192
+ return `
193
+ <div class="bg-white border border-gray-200 rounded-lg shadow">
194
+ <!-- Header (clickable) -->
195
+ <button
196
+ class="w-full text-left p-4 flex items-start justify-between gap-3 hover:bg-gray-50 rounded-t-lg"
197
+ onclick="toggleBox('${id}')"
198
+ >
199
+ <div class="min-w-0">
200
+ <div class="text-xs text-gray-500">${created || "—"}</div>
201
+ <div class="font-semibold text-green-700 truncate">${reportName}</div>
202
+ ${
203
+ top3.length
204
+ ? `<div class="flex flex-wrap gap-2 mt-2">
205
+ ${top3
206
+ .map(
207
+ (t) => `
208
+ <span class="text-xs bg-gray-100 border border-gray-200 px-2 py-1 rounded">
209
+ ${t}
210
+ </span>`
211
+ )
212
+ .join("")}
213
+ </div>`
214
+ : `<div class="text-xs text-gray-400 mt-2">No anomalies detected</div>`
215
+ }
216
  </div>
217
+ <span id="box-arrow-${id}" class="text-gray-500 transition-transform select-none mt-1">Open Full View</span>
218
+ </button>
219
+
220
+ <!-- Collapsible body -->
221
+ <div
222
+ id="box-content-${id}"
223
+ class="overflow-hidden transition-all duration-300 max-h-0 border-t border-gray-200"
224
+ >
225
+ <div class="p-4 space-y-3">
226
+ <!-- OCR -->
227
+ <div>
228
+ <div class="font-medium mb-1">OCR Text</div>
229
+ <pre class="whitespace-pre-wrap text-sm text-gray-700 bg-gray-50 border rounded p-3">${
230
+ item?.ocr_text || ""
231
  }</pre>
232
  </div>
233
+
234
+ <!-- Full anomalies list -->
235
+ ${
236
+ anomalies.length
237
+ ? `
238
+ <div class="pt-2 border-t border-gray-100">
239
+ <div class="font-medium mb-2">Findings</div>
240
+ ${anomalies
241
+ .map(
242
+ (a, i) => `
243
+ <div class="border-t border-gray-200 pt-2 mt-2">
244
+ <div class="font-medium">Finding ${i + 1}: ${a?.findings ?? ""}</div>
245
+ <div class="text-sm text-gray-600">Severity: ${a?.severity ?? ""}</div>
246
+ <div class="text-sm text-gray-600">Recommendations: ${
247
+ Array.isArray(a?.recommendations)
248
+ ? a.recommendations.join(", ")
249
+ : ""
250
+ }</div>
251
+ </div>`
252
+ )
253
+ .join("")}
254
+ </div>
255
+ `
256
+ : ""
257
+ }
258
  </div>
259
  </div>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
260
  </div>
261
  `;
262
+ }
263
+
264
+
265
+ function renderBackendRow(row) {
266
+ const created = row.created_at ? new Date(row.created_at).toLocaleString() : '';
267
+ const reportDate = row.report_date || 'N/A';
268
+ const anomalies = (() => {
269
+ try { return Array.isArray(row.anomalies) ? row.anomalies : JSON.parse(row.anomalies || '[]'); }
270
+ catch { return []; }
271
+ })();
272
 
273
+ return `
274
+ <div class="bg-white border border-gray-200 rounded-lg p-4 shadow">
275
+ <div class="flex justify-between mb-2">
276
+ <div class="font-semibold text-green-700">Report: ${reportDate}</div>
277
+ <div class="text-xs text-gray-500">${created}</div>
278
+ </div>
279
+ <pre class="whitespace-pre-wrap text-sm text-gray-700 mb-2">${row.ocr_text || ''}</pre>
280
+ ${anomalies.map((r,i)=>`
281
+ <div class="border-t border-gray-200 pt-2 mt-2">
282
+ <div class="font-medium">Finding ${i+1}: ${r?.findings ?? ''}</div>
283
+ <div class="text-sm text-gray-600">Severity: ${r?.severity ?? ''}</div>
284
+ <div class="text-sm text-gray-600">Recommendations: ${(Array.isArray(r?.recommendations)?r.recommendations:[]).join(', ')}</div>
285
+ </div>
286
+ `).join('')}
287
+ </div>`;
288
+ }
289
+
290
+ function renderList(htmlItems) {
291
+ recsEl.innerHTML = htmlItems && htmlItems.length
292
+ ? htmlItems.join('')
293
+ : '<p class="text-sm text-gray-500">No saved analyses yet.</p>';
294
+ }
295
+
296
+ async function loadFromBackend(user) {
297
+ try {
298
+ const userId = user.email || user.uid;
299
+ const url = api('reports/', { user_id: userId });
300
+ dbg('Backend URL', url);
301
+ const res = await fetch(url, { method: 'GET' });
302
+ dbg('Backend HTTP status', res.status);
303
+ if (!res.ok) throw new Error(`HTTP ${res.status}`);
304
+ const data = await res.json();
305
+ dbg('Backend payload', data);
306
+ return Array.isArray(data) ? data : [];
307
+ } catch (e) {
308
+ console.error('Backend fetch failed:', e);
309
+ dbg('Backend fetch error', e.message || e);
310
+ return [];
311
+ }
312
+ }
313
+
314
+ async function loadFromFirestore(user) {
315
+ try {
316
+ const q = query(collection(db, 'users', user.uid, 'analyses'), orderBy('createdAt', 'desc'));
317
+ dbg('Firestore query uid', user.uid);
318
+ const snap = await getDocs(q);
319
+ const rows = snap.docs.map(d => d.data());
320
+ dbg('Firestore count', rows.length);
321
+ return rows;
322
+ } catch (e) {
323
+ console.error('Firestore fetch failed:', e);
324
+ dbg('Firestore fetch error', e.message || e);
325
+ return [];
326
+ }
327
+ }
328
+
329
+ onAuthStateChanged(auth, async (user) => {
330
+ if (!user) {
331
+ statusEl.textContent = 'Not signed in.';
332
+ renderList([]);
333
+ setDebugBanner('User is not signed in.');
334
+ return;
335
  }
336
 
337
+ statusEl.textContent = `Signed in as ${user.email || user.uid}`;
338
+
339
+ const backendRows = await loadFromBackend(user);
340
+ const firestoreRows = await loadFromFirestore(user);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
341
 
342
+ setDebugBanner(`backend: ${backendRows.length} | firestore: ${firestoreRows.length}`);
343
+ dbg('Summary', { backend: backendRows.length, firestore: firestoreRows.length });
344
+
345
+ if (backendRows.length > 0) {
346
+ recsEl.innerHTML = backendRows.map((row, i) => renderBackendReportCollapsible(row, i)).join("");
347
+ } else if (firestoreRows.length > 0) {
348
+ recsEl.innerHTML = firestoreRows.map((row, i) => renderFirestoreReportCollapsible(row, i)).join("");
349
+ } else {
350
+ recsEl.innerHTML = '<p class="text-sm text-gray-500">No saved analyses yet.</p>';
351
+ }
352
+ });
353
+ </script>
354
+ </body>
355
+ </html>