From 9e0a2d07c0a2a80f71649a27c3a1a525c70501fc Mon Sep 17 00:00:00 2001 From: kushwahnihal25-rgb Date: Fri, 12 Jun 2026 18:36:36 +0530 Subject: [PATCH 1/2] Add shimmer loading skeleton for analysis dashboard --- src/components/shared/ScanSkeleton.tsx | 33 ++++++++++++++++++++++++++ src/index.css | 21 ++++++++++++++++ src/pages/AnalysisDashboard.tsx | 14 +++++------ 3 files changed, 60 insertions(+), 8 deletions(-) create mode 100644 src/components/shared/ScanSkeleton.tsx diff --git a/src/components/shared/ScanSkeleton.tsx b/src/components/shared/ScanSkeleton.tsx new file mode 100644 index 0000000..60559fd --- /dev/null +++ b/src/components/shared/ScanSkeleton.tsx @@ -0,0 +1,33 @@ +import GlassCard from "../GlassCard"; +export default function ScanSkeleton() { + return ( +
+
+ +
+
+
+
+ + +
+
+
+
+
+ + +
+
+
+
+
+
+
+ + +
+
+
+ ); +} \ No newline at end of file diff --git a/src/index.css b/src/index.css index e1d1223..055a6c0 100644 --- a/src/index.css +++ b/src/index.css @@ -396,4 +396,25 @@ input::placeholder { @media print { nav, .print\:hidden { display: none !important; } +} +@keyframes shimmer { + 0% { + background-position: -200% 0; + } + + 100% { + background-position: 200% 0; + } +} + +.skeleton-shimmer { + background: linear-gradient( + 90deg, + var(--color-surface-mid) 25%, + var(--color-surface-highest) 50%, + var(--color-surface-mid) 75% + ); + + background-size: 200% 100%; + animation: shimmer 1.5s linear infinite; } \ No newline at end of file diff --git a/src/pages/AnalysisDashboard.tsx b/src/pages/AnalysisDashboard.tsx index 771227f..dd4eca6 100644 --- a/src/pages/AnalysisDashboard.tsx +++ b/src/pages/AnalysisDashboard.tsx @@ -5,6 +5,7 @@ import GlassCard from '../components/GlassCard'; import StatusTerminal from '../components/StatusTerminal'; import { api } from '../lib/api'; import type { ScanResult } from '../lib/types'; +import ScanSkeleton from "../components/shared/ScanSkeleton"; const BIOMARKER_META = { gill_saturation: { label: 'Gill Saturation', icon: Droplets }, @@ -21,6 +22,7 @@ function gradeColor(grade: string) { } export default function AnalysisDashboard() { + const [params] = useSearchParams(); const [scan, setScan] = useState(null); const [loading, setLoading] = useState(true); @@ -43,20 +45,16 @@ export default function AnalysisDashboard() { } catch (err) { setError(err instanceof Error ? err.message : 'Failed to load scan data.'); } finally { - setLoading(false); + setLoading(false); } } load(); }, [params]); // ── Loading state ──────────────────────────────────────────────────────── - if (loading) { - return ( -
- -
- ); - } +if (loading) { + return ; +} // ── Error state ────────────────────────────────────────────────────────── if (error || !scan) { From 4c5836be02a31c1ba857f2523c1e08625811c27f Mon Sep 17 00:00:00 2001 From: Karan Singh Date: Thu, 18 Jun 2026 21:34:31 +0530 Subject: [PATCH 2/2] fix: properly resolve merge conflicts and preserve offline mode fixes --- src/pages/AnalysisDashboard.tsx | 39 ++++++++++++++++++------- src/pages/ScannerPage.tsx | 51 +++++++++++++++++++++++++++++++++ 2 files changed, 80 insertions(+), 10 deletions(-) diff --git a/src/pages/AnalysisDashboard.tsx b/src/pages/AnalysisDashboard.tsx index dd4eca6..14d4f89 100644 --- a/src/pages/AnalysisDashboard.tsx +++ b/src/pages/AnalysisDashboard.tsx @@ -6,7 +6,6 @@ import StatusTerminal from '../components/StatusTerminal'; import { api } from '../lib/api'; import type { ScanResult } from '../lib/types'; import ScanSkeleton from "../components/shared/ScanSkeleton"; - const BIOMARKER_META = { gill_saturation: { label: 'Gill Saturation', icon: Droplets }, corneal_clarity: { label: 'Corneal Clarity', icon: EyeIcon }, @@ -22,19 +21,20 @@ function gradeColor(grade: string) { } export default function AnalysisDashboard() { - const [params] = useSearchParams(); const [scan, setScan] = useState(null); const [loading, setLoading] = useState(true); const [error, setError] = useState(''); useEffect(() => { + async function load() { setLoading(true); - setError(''); + setError(""); + try { - const idParam = params.get('id'); - const lastId = sessionStorage.getItem('lastScanId'); + const idParam = params.get("id"); + const lastId = sessionStorage.getItem("lastScanId"); const targetId = idParam || lastId; const res = targetId @@ -43,18 +43,37 @@ export default function AnalysisDashboard() { setScan(res.scan); } catch (err) { - setError(err instanceof Error ? err.message : 'Failed to load scan data.'); + const offlineData = sessionStorage.getItem("offlineScanResult"); + + if (offlineData) { + try { + const parsed = JSON.parse(offlineData); + + if (parsed?.freshness_index != null) { + setScan(parsed); + setLoading(false); + return; + } + } catch (e) { + console.warn("Failed to parse offline scan result", e); + } + } + + setError( + err instanceof Error ? err.message : "Failed to load scan data." + ); } finally { - setLoading(false); + setLoading(false); } } + load(); }, [params]); // ── Loading state ──────────────────────────────────────────────────────── -if (loading) { - return ; -} + if (loading) { + return ; + } // ── Error state ────────────────────────────────────────────────────────── if (error || !scan) { diff --git a/src/pages/ScannerPage.tsx b/src/pages/ScannerPage.tsx index 58af502..4f5e4e3 100644 --- a/src/pages/ScannerPage.tsx +++ b/src/pages/ScannerPage.tsx @@ -253,6 +253,57 @@ export default function ScannerPage() { stopProgress(100); const freshness = Math.round(fusion.fusedScore * 100); + const offlineScanResult = { + scan_id: "offline-scan", + scan_display_id: "OFFLINE_SCAN", + freshness_index: freshness, + grade: deriveGrade(freshness), + confidence: parseFloat(fusion.confidence), + classification: + fusion.label === "Fresh" + ? "FRESH" + : fusion.label === "Moderate" + ? "FRESH" + : "SPOILED", + is_fresh: fusion.label !== "Spoiled", + uncertain_flag: false, + species: { + common_name: "Unknown Fish", + scientific_name: "N/A", + habitat: "N/A", + tags: [], + weight_estimate_kg: 0, + catch_age_hours: 0, + }, + biomarkers: { + gill_saturation: { + score: freshness, + status: "NOMINAL", + detail: "Offline edge inference", + }, + corneal_clarity: { + score: freshness, + status: "NOMINAL", + detail: "Offline edge inference", + }, + epidermal_tension: { + score: freshness, + status: "NOMINAL", + detail: "Offline edge inference", + }, + }, + recommendations: { + consume_within_hours: 0, + storage_temp: "Unknown", + alert_flags: ["Generated from offline scan"], + }, + }; + + sessionStorage.setItem( + "offlineScanResult", + JSON.stringify(offlineScanResult), + ); + setResult({ label: fusion.label, freshness,