Yassine Mhirsi
commited on
Commit
·
6c8fc4b
1
Parent(s):
5f83845
Add caching support for analysis data in AnalysisPage
Browse files- Implemented caching utilities to store and retrieve analysis results using localStorage.
- Updated AnalysisPage to utilize cached data for improved loading performance and user experience.
- Added loading state indication for data refresh in the UI.
- Enhanced error handling to display errors only when no cached data is available.
- src/app/pages/AnalysisPage.tsx +40 -10
- src/app/utils/cache.utils.ts +72 -0
- src/app/utils/index.ts +3 -0
src/app/pages/AnalysisPage.tsx
CHANGED
|
@@ -1,5 +1,5 @@
|
|
| 1 |
import React, { useMemo, useState, useEffect } from 'react';
|
| 2 |
-
import { parseCsv, formatError } from '../utils/index.ts';
|
| 3 |
import { CsvRow } from '../types/index.ts';
|
| 4 |
import { AnalysisResult } from '../types/analysis.types.ts';
|
| 5 |
import { analyzeArgumentsFromCsv, getAnalysisResults } from '../services/analysis.service.ts';
|
|
@@ -28,6 +28,7 @@ const AnalysisPage: React.FC = () => {
|
|
| 28 |
// User's historical analysis data
|
| 29 |
const [userAnalysisData, setUserAnalysisData] = useState<AnalysisResult[]>([]);
|
| 30 |
const [isLoadingStats, setIsLoadingStats] = useState<boolean>(true);
|
|
|
|
| 31 |
const [statsError, setStatsError] = useState<string | null>(null);
|
| 32 |
|
| 33 |
const fileName = useMemo(
|
|
@@ -35,18 +36,35 @@ const AnalysisPage: React.FC = () => {
|
|
| 35 |
[selectedFile]
|
| 36 |
);
|
| 37 |
|
| 38 |
-
// Fetch user's analysis data on mount
|
| 39 |
useEffect(() => {
|
| 40 |
const fetchUserAnalysis = async () => {
|
| 41 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 42 |
setStatsError(null);
|
|
|
|
|
|
|
| 43 |
try {
|
| 44 |
const response = await getAnalysisResults(1000, 0);
|
| 45 |
setUserAnalysisData(response.results);
|
|
|
|
|
|
|
| 46 |
} catch (err) {
|
| 47 |
-
|
|
|
|
|
|
|
|
|
|
| 48 |
} finally {
|
| 49 |
setIsLoadingStats(false);
|
|
|
|
| 50 |
}
|
| 51 |
};
|
| 52 |
|
|
@@ -131,6 +149,8 @@ const AnalysisPage: React.FC = () => {
|
|
| 131 |
// Refresh user analysis data after successful analysis
|
| 132 |
const updatedResponse = await getAnalysisResults(1000, 0);
|
| 133 |
setUserAnalysisData(updatedResponse.results);
|
|
|
|
|
|
|
| 134 |
} catch (err) {
|
| 135 |
setError(formatError(err));
|
| 136 |
} finally {
|
|
@@ -144,12 +164,22 @@ const AnalysisPage: React.FC = () => {
|
|
| 144 |
{/* Statistics Section */}
|
| 145 |
<div className="rounded-lg border border-slate-200 dark:border-zinc-700 bg-white dark:bg-zinc-900 shadow-sm">
|
| 146 |
<div className="border-b border-slate-200 dark:border-zinc-700 px-6 py-4">
|
| 147 |
-
<
|
| 148 |
-
|
| 149 |
-
|
| 150 |
-
|
| 151 |
-
|
| 152 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 153 |
</div>
|
| 154 |
|
| 155 |
{isLoadingStats ? (
|
|
|
|
| 1 |
import React, { useMemo, useState, useEffect } from 'react';
|
| 2 |
+
import { parseCsv, formatError, getCachedAnalysisData, cacheAnalysisData } from '../utils/index.ts';
|
| 3 |
import { CsvRow } from '../types/index.ts';
|
| 4 |
import { AnalysisResult } from '../types/analysis.types.ts';
|
| 5 |
import { analyzeArgumentsFromCsv, getAnalysisResults } from '../services/analysis.service.ts';
|
|
|
|
| 28 |
// User's historical analysis data
|
| 29 |
const [userAnalysisData, setUserAnalysisData] = useState<AnalysisResult[]>([]);
|
| 30 |
const [isLoadingStats, setIsLoadingStats] = useState<boolean>(true);
|
| 31 |
+
const [isRefreshingStats, setIsRefreshingStats] = useState<boolean>(false);
|
| 32 |
const [statsError, setStatsError] = useState<string | null>(null);
|
| 33 |
|
| 34 |
const fileName = useMemo(
|
|
|
|
| 36 |
[selectedFile]
|
| 37 |
);
|
| 38 |
|
| 39 |
+
// Fetch user's analysis data on mount with cache support
|
| 40 |
useEffect(() => {
|
| 41 |
const fetchUserAnalysis = async () => {
|
| 42 |
+
// Load cached data immediately
|
| 43 |
+
const cachedData = getCachedAnalysisData();
|
| 44 |
+
if (cachedData && cachedData.length > 0) {
|
| 45 |
+
setUserAnalysisData(cachedData);
|
| 46 |
+
setIsLoadingStats(false);
|
| 47 |
+
setIsRefreshingStats(true);
|
| 48 |
+
} else {
|
| 49 |
+
setIsLoadingStats(true);
|
| 50 |
+
}
|
| 51 |
+
|
| 52 |
setStatsError(null);
|
| 53 |
+
|
| 54 |
+
// Fetch fresh data in the background
|
| 55 |
try {
|
| 56 |
const response = await getAnalysisResults(1000, 0);
|
| 57 |
setUserAnalysisData(response.results);
|
| 58 |
+
// Cache the fresh data
|
| 59 |
+
cacheAnalysisData(response.results);
|
| 60 |
} catch (err) {
|
| 61 |
+
// Only show error if we don't have cached data
|
| 62 |
+
if (!cachedData || cachedData.length === 0) {
|
| 63 |
+
setStatsError(formatError(err));
|
| 64 |
+
}
|
| 65 |
} finally {
|
| 66 |
setIsLoadingStats(false);
|
| 67 |
+
setIsRefreshingStats(false);
|
| 68 |
}
|
| 69 |
};
|
| 70 |
|
|
|
|
| 149 |
// Refresh user analysis data after successful analysis
|
| 150 |
const updatedResponse = await getAnalysisResults(1000, 0);
|
| 151 |
setUserAnalysisData(updatedResponse.results);
|
| 152 |
+
// Cache the updated data
|
| 153 |
+
cacheAnalysisData(updatedResponse.results);
|
| 154 |
} catch (err) {
|
| 155 |
setError(formatError(err));
|
| 156 |
} finally {
|
|
|
|
| 164 |
{/* Statistics Section */}
|
| 165 |
<div className="rounded-lg border border-slate-200 dark:border-zinc-700 bg-white dark:bg-zinc-900 shadow-sm">
|
| 166 |
<div className="border-b border-slate-200 dark:border-zinc-700 px-6 py-4">
|
| 167 |
+
<div className="flex items-center justify-between">
|
| 168 |
+
<div>
|
| 169 |
+
<h2 className="text-lg font-semibold text-slate-800 dark:text-white">
|
| 170 |
+
Your Analysis Statistics
|
| 171 |
+
</h2>
|
| 172 |
+
<p className="text-sm text-slate-500 dark:text-zinc-400">
|
| 173 |
+
Overview of all your analyzed arguments
|
| 174 |
+
</p>
|
| 175 |
+
</div>
|
| 176 |
+
{isRefreshingStats && (
|
| 177 |
+
<div className="flex items-center gap-2 text-xs text-slate-500 dark:text-zinc-400">
|
| 178 |
+
<div className="h-3 w-3 animate-spin rounded-full border-2 border-slate-300 dark:border-zinc-600 border-t-blue-600 dark:border-t-blue-500"></div>
|
| 179 |
+
<span>Updating...</span>
|
| 180 |
+
</div>
|
| 181 |
+
)}
|
| 182 |
+
</div>
|
| 183 |
</div>
|
| 184 |
|
| 185 |
{isLoadingStats ? (
|
src/app/utils/cache.utils.ts
ADDED
|
@@ -0,0 +1,72 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
/**
|
| 2 |
+
* Cache utilities for storing and retrieving analysis data
|
| 3 |
+
*/
|
| 4 |
+
|
| 5 |
+
import type { AnalysisResult } from '../types/analysis.types.ts';
|
| 6 |
+
|
| 7 |
+
const CACHE_KEY = 'analysis_data_cache';
|
| 8 |
+
const CACHE_TIMESTAMP_KEY = 'analysis_data_cache_timestamp';
|
| 9 |
+
|
| 10 |
+
type CachedAnalysisData = {
|
| 11 |
+
results: AnalysisResult[];
|
| 12 |
+
timestamp: number;
|
| 13 |
+
};
|
| 14 |
+
|
| 15 |
+
/**
|
| 16 |
+
* Cache analysis results to localStorage
|
| 17 |
+
*/
|
| 18 |
+
export function cacheAnalysisData(results: AnalysisResult[]): void {
|
| 19 |
+
try {
|
| 20 |
+
const cacheData: CachedAnalysisData = {
|
| 21 |
+
results,
|
| 22 |
+
timestamp: Date.now(),
|
| 23 |
+
};
|
| 24 |
+
localStorage.setItem(CACHE_KEY, JSON.stringify(cacheData));
|
| 25 |
+
localStorage.setItem(CACHE_TIMESTAMP_KEY, String(cacheData.timestamp));
|
| 26 |
+
} catch (error) {
|
| 27 |
+
// Silently fail if localStorage is not available or quota exceeded
|
| 28 |
+
console.warn('Failed to cache analysis data:', error);
|
| 29 |
+
}
|
| 30 |
+
}
|
| 31 |
+
|
| 32 |
+
/**
|
| 33 |
+
* Get cached analysis results from localStorage
|
| 34 |
+
*/
|
| 35 |
+
export function getCachedAnalysisData(): AnalysisResult[] | null {
|
| 36 |
+
try {
|
| 37 |
+
const cached = localStorage.getItem(CACHE_KEY);
|
| 38 |
+
if (!cached) return null;
|
| 39 |
+
|
| 40 |
+
const cacheData: CachedAnalysisData = JSON.parse(cached);
|
| 41 |
+
return cacheData.results;
|
| 42 |
+
} catch (error) {
|
| 43 |
+
// Silently fail if localStorage is not available or data is corrupted
|
| 44 |
+
console.warn('Failed to retrieve cached analysis data:', error);
|
| 45 |
+
return null;
|
| 46 |
+
}
|
| 47 |
+
}
|
| 48 |
+
|
| 49 |
+
/**
|
| 50 |
+
* Get cache timestamp
|
| 51 |
+
*/
|
| 52 |
+
export function getCacheTimestamp(): number | null {
|
| 53 |
+
try {
|
| 54 |
+
const timestamp = localStorage.getItem(CACHE_TIMESTAMP_KEY);
|
| 55 |
+
return timestamp ? Number(timestamp) : null;
|
| 56 |
+
} catch (error) {
|
| 57 |
+
return null;
|
| 58 |
+
}
|
| 59 |
+
}
|
| 60 |
+
|
| 61 |
+
/**
|
| 62 |
+
* Clear cached analysis data
|
| 63 |
+
*/
|
| 64 |
+
export function clearAnalysisCache(): void {
|
| 65 |
+
try {
|
| 66 |
+
localStorage.removeItem(CACHE_KEY);
|
| 67 |
+
localStorage.removeItem(CACHE_TIMESTAMP_KEY);
|
| 68 |
+
} catch (error) {
|
| 69 |
+
console.warn('Failed to clear analysis cache:', error);
|
| 70 |
+
}
|
| 71 |
+
}
|
| 72 |
+
|
src/app/utils/index.ts
CHANGED
|
@@ -10,6 +10,9 @@ export * from './user.utils.ts';
|
|
| 10 |
// Export analysis utilities
|
| 11 |
export * from './analysis.utils.ts';
|
| 12 |
|
|
|
|
|
|
|
|
|
|
| 13 |
/**
|
| 14 |
* Debounce function - delays execution until after wait time
|
| 15 |
*/
|
|
|
|
| 10 |
// Export analysis utilities
|
| 11 |
export * from './analysis.utils.ts';
|
| 12 |
|
| 13 |
+
// Export cache utilities
|
| 14 |
+
export * from './cache.utils.ts';
|
| 15 |
+
|
| 16 |
/**
|
| 17 |
* Debounce function - delays execution until after wait time
|
| 18 |
*/
|