meamgodyay commited on
Commit
1cf3818
·
verified ·
1 Parent(s): e871e34

Update web/past_data.html

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