Skip to content

feat(overview): Implement Stats Caching and Graph Updates#25

Open
pranjal29092005 wants to merge 3 commits intoDjedAlliance:mainfrom
pranjal29092005:feat/overview-stats-caching
Open

feat(overview): Implement Stats Caching and Graph Updates#25
pranjal29092005 wants to merge 3 commits intoDjedAlliance:mainfrom
pranjal29092005:feat/overview-stats-caching

Conversation

@pranjal29092005
Copy link

@pranjal29092005 pranjal29092005 commented Dec 14, 2025

Description

This PR implements stats caching and graph updates for the Overview tab as requested in #5.

Changes

  • ✅ Add localStorage caching for stats with instant display on page load
  • ✅ Calculate and display all 5 stats: Transactions, Revenue, Success Rate, Failed, Pending
  • ✅ Show loading indicator while fetching fresh data (cached values remain visible)
  • ✅ Update graph component to use real transaction data instead of mock data
  • ✅ Transform transaction data by time period (week/month/year) for chart display
  • ✅ Handle fetch failures gracefully without clearing cached data
  • ✅ Ensure refresh-safe behavior with no UI flicker

Files Modified

  • dashboard/app/page.tsx - Overview page with stats caching
  • dashboard/components/dashboard/chart/index.tsx - Chart with real data

Testing

  • ✅ Build passes: npm run build
  • ✅ No TypeScript errors
  • ✅ Linting passes
  • ✅ Verified stats cache on page refresh
  • ✅ Verified graph updates with real data
  • ✅ Verified loading states

How to Test

  1. Navigate to Overview tab (should show "T/A" for all stats)
  2. Go to Transactions tab, click "See Transactions"
  3. Return to Overview tab - all stats should display real values
  4. Refresh the page - stats should appear instantly from cache
  5. Click "Refresh Data" in Transactions tab
  6. Check Overview tab - should show loading banner with cached values visible
  7. Switch between WEEK/MONTH/YEAR tabs on graph - should show real data

Closes #5

Summary by CodeRabbit

  • New Features

    • Dashboard statistics now persist locally so cached metrics load instantly on return.
    • Charts use real transaction data for week/month/year views instead of sample data.
    • A refresh banner appears while fresh dashboard data is being fetched.
  • Improvements

    • Cached stats display immediately and are replaced by fresh values when available.
    • Header shows timestamped cached data; UI consistently shows placeholders when no data exists.

✏️ Tip: You can customize this high-level summary in your review settings.

@coderabbitai
Copy link

coderabbitai bot commented Dec 14, 2025

Walkthrough

Adds localStorage-backed client-side caching for Overview stats, displays cached values immediately while fetching fresh transactions, updates cache and UI when fresh data arrives, and replaces mock chart data with transformed transaction-derived data for week/month/year views.

Changes

Cohort / File(s) Summary
Dashboard Stats Caching
dashboard/app/page.tsx
Adds STATS_CACHE_KEY, expiry and CachedStats shape; reads cached stats on mount, exposes cachedStats, isLoadingFresh, and hasAnyData; shows cached values (or "T/A") immediately with an updating banner while fresh transactions fetch; computes fresh stats from transactions and persists updated cache with timestamp.
Chart Data Transformation
dashboard/components/dashboard/chart/index.tsx
Imports useTransactions, adds FEE_PERCENTAGE, implements transformToChartData(period) to bucket/aggregate transactions into chart-ready data (dates, revenue, tx count, fees); memoizes week/month/year chartData and falls back to mock data until transactions are fetched; integrates transformed data into TabsContent.
sequenceDiagram
    autonumber
    participant Browser as Browser (DashboardOverview)
    participant Local as localStorage
    participant Hook as useTransactions (fetch)
    participant Chart as Chart Component

    Note over Browser,Local: On mount
    Browser->>Local: read STATS_CACHE_KEY
    Local-->>Browser: cachedStats or null

    Browser->>Chart: render with cachedStats (or "T/A")
    Browser->>Hook: start fetch transactions (async)
    Hook-->>Browser: hasFetched=false (initial)

    alt while fetching
        Browser->>Browser: show cached values + updating banner
        Chart->>Chart: render with cached/placeholder data
    end

    Hook-->>Browser: fresh transactions (success)
    Browser->>Browser: compute real stats, update state
    Browser->>Local: write STATS_CACHE_KEY with timestamp
    Browser->>Chart: provide transformed chartData (week/month/year)
    Chart-->>Browser: re-render with fresh data

    alt fetch fails
        Hook-->>Browser: error
        Browser->>Browser: keep cachedStats, show error/stop updating banner
    end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

  • Focus areas:
    • dashboard/app/page.tsx: localStorage read/write, expiry handling, isLoadingFresh / hasAnyData transitions, formatting/units for displayed stats.
    • dashboard/components/dashboard/chart/index.tsx: date bucketing in transformToChartData, fee/revenue calculations, memoization and fallback to mock data.

Poem

🐰
I nibbled at cache beneath the moon,
Fetched fresh carrots to update soon,
No flicker, just a gentle hum,
Charts and stats hop—here they come! 🥕📊

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately describes the main changes: implementing stats caching and graph updates for the Overview tab, which aligns with the primary objectives.
Linked Issues check ✅ Passed The pull request implements all primary coding requirements from issue #5: stats caching with localStorage, displaying five stats with cached/fresh data flow, graph updates with real transaction data, loading indicators, graceful failure handling, and refresh-safe behavior.
Out of Scope Changes check ✅ Passed All changes are scoped to the Overview tab as required: dashboard/app/page.tsx for stats caching and dashboard/components/dashboard/chart/index.tsx for chart updates; no unrelated modifications detected.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

📜 Recent review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between eea7ad7 and 5d489af.

📒 Files selected for processing (1)
  • dashboard/app/page.tsx (4 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • dashboard/app/page.tsx

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🧹 Nitpick comments (4)
dashboard/components/dashboard/chart/index.tsx (3)

93-94: Consider making the fee percentage configurable.

The 1% fee is hardcoded. If the actual fee structure differs or changes, this will display incorrect data. Consider extracting this to a constant or configuration value.

+const FEE_PERCENTAGE = 0.01; // 1% fee - adjust as needed
+
 // Inside transformToChartData:
       const revenue = parseFloat(tx.amountBC);
-      const fees = revenue * 0.01; // Assume 1% fee
+      const fees = revenue * FEE_PERCENTAGE;

71-85: Year period iterates 365 times but only needs 12 months.

For the "year" period, the loop runs 365 times but dateFormat returns only the month name (e.g., "Jan"). This creates redundant iterations since the Map deduplicates by key. Consider iterating by month instead for better efficiency.

+    // For year, iterate by month instead of day
+    if (period === "year") {
+      for (let i = 11; i >= 0; i--) {
+        const date = new Date(now.getFullYear(), now.getMonth() - i, 1);
+        const dateKey = dateFormat(date);
+        if (!dataPoints.has(dateKey)) {
+          dataPoints.set(dateKey, { revenue: 0, count: 0, fees: 0 });
+        }
+      }
+    } else {
       // Initialize all dates with zero values
       for (let i = daysToInclude - 1; i >= 0; i--) {
         const date = new Date(now);
         date.setDate(date.getDate() - i);
         const dateKey = dateFormat(date);

         if (!dataPoints.has(dateKey)) {
           dataPoints.set(dateKey, { revenue: 0, count: 0, fees: 0 });
         }
       }
+    }

269-277: Memoize chart data transformation to avoid recalculating on every render.

transformToChartData is called for all three tabs on every render, reprocessing all transactions each time. Consider memoizing the results with useMemo.

+  const chartData = React.useMemo(() => ({
+    week: transformToChartData("week"),
+    month: transformToChartData("month"),
+    year: transformToChartData("year"),
+  }), [transactions, hasFetched]);
+
   // In JSX:
   <TabsContent value="week" className="space-y-4">
-    {renderChart(transformToChartData("week"))}
+    {renderChart(chartData.week)}
   </TabsContent>
   <TabsContent value="month" className="space-y-4">
-    {renderChart(transformToChartData("month"))}
+    {renderChart(chartData.month)}
   </TabsContent>
   <TabsContent value="year" className="space-y-4">
-    {renderChart(transformToChartData("year"))}
+    {renderChart(chartData.year)}
   </TabsContent>
dashboard/app/page.tsx (1)

24-34: Consider adding cache expiry for stats.

Unlike the transactions cache in useTransactions which has a CACHE_EXPIRY, the stats cache has no expiry mechanism. Very old cached stats could be misleading to users. Consider adding expiry validation similar to the transactions hook.

 const STATS_CACHE_KEY = 'stablepay_stats_cache';
+const STATS_CACHE_EXPIRY = 24 * 60 * 60 * 1000; // 24 hours

 // In useEffect:
   if (cached) {
     try {
       const parsedStats: CachedStats = JSON.parse(cached);
+      const isExpired = Date.now() - parsedStats.timestamp > STATS_CACHE_EXPIRY;
+      if (!isExpired) {
         setCachedStats(parsedStats);
+      }
     } catch (err) {
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between c12f638 and 8abac64.

📒 Files selected for processing (2)
  • dashboard/app/page.tsx (4 hunks)
  • dashboard/components/dashboard/chart/index.tsx (3 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
dashboard/components/dashboard/chart/index.tsx (2)
dashboard/hooks/use-transactions.ts (1)
  • useTransactions (13-85)
dashboard/types/dashboard.ts (2)
  • TimePeriod (30-30)
  • ChartDataPoint (11-16)
🔇 Additional comments (7)
dashboard/components/dashboard/chart/index.tsx (1)

44-44: LGTM on hook integration.

The useTransactions hook is correctly integrated to provide transactions and hasFetched state for data-driven chart rendering.

dashboard/app/page.tsx (6)

41-52: LGTM on cache loading.

The cache loading effect correctly runs once on mount with proper error handling for malformed cached data.


54-59: LGTM on stats calculation.

The calculation logic is straightforward. The comments clearly explain that failed/pending are always 0 for blockchain data.


61-76: LGTM on cache update logic.

The effect correctly updates localStorage when fresh data is fetched. The dependency array, while verbose, satisfies React's exhaustive-deps rule.


85-100: LGTM on display stats logic.

The fallback chain (fresh → cached → defaults) correctly implements the PR requirement to show cached values instantly while fresh data loads.


103-141: LGTM on stats configuration.

The stats array correctly implements all five required statistics with appropriate fallbacks to "T/A" when no data is available.


143-169: LGTM on conditional UI banners.

The header description and conditional banners correctly communicate the data state to users (no data, cached data with timestamp, or real-time data).

… resets on fetch completion/failure) - Fix transaction date bug (use tx.timestamp instead of new Date()) - Add 24-hour cache expiry for stats to prevent stale data - Make fee percentage configurable (FEE_PERCENTAGE constant) - Optimize year period iteration (iterate by month instead of 365 days) - Memoize chart data transformation to avoid unnecessary recalculations
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (4)
dashboard/app/page.tsx (4)

24-35: Cache constants/types look fine; consider namespacing the key to env/app.
If you run multiple dashboard apps on the same origin, a more specific key (e.g., including app name + chain/network) reduces collision risk.


42-56: Guard localStorage reads for “unavailable storage” cases.
getItem can throw in some browsers/modes; you already handle JSON parse errors, but not storage access errors.

  useEffect(() => {
-   const cached = localStorage.getItem(STATS_CACHE_KEY);
-   if (cached) {
-     try {
-       const parsedStats: CachedStats = JSON.parse(cached);
-       const isExpired = Date.now() - parsedStats.timestamp > STATS_CACHE_EXPIRY;
-       if (!isExpired) {
-         setCachedStats(parsedStats);
-       }
-     } catch (err) {
-       console.warn('Failed to parse cached stats:', err);
-     }
-   }
+   try {
+     const cached = localStorage.getItem(STATS_CACHE_KEY);
+     if (!cached) return;
+     const parsedStats: CachedStats = JSON.parse(cached);
+     const isExpired = Date.now() - parsedStats.timestamp > STATS_CACHE_EXPIRY;
+     if (!isExpired) setCachedStats(parsedStats);
+   } catch (err) {
+     console.warn("Failed to load cached stats:", err);
+   }
  }, []);

58-64: Revenue math via parseFloat may introduce precision drift.
If tx.amountBC is currency-like, consider using an integer base unit (or a decimal/bignumber library) and format at the end.


155-160: Header/banner messaging is coherent with cached-first UX.
Minor: consider whether emojis are desired in the dashboard UI copy (“Note”, “Updating”).

Also applies to: 163-177

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 8abac64 and eea7ad7.

📒 Files selected for processing (2)
  • dashboard/app/page.tsx (4 hunks)
  • dashboard/components/dashboard/chart/index.tsx (3 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • dashboard/components/dashboard/chart/index.tsx
🧰 Additional context used
🧬 Code graph analysis (1)
dashboard/app/page.tsx (1)
dashboard/hooks/use-transactions.ts (1)
  • useTransactions (13-85)
🔇 Additional comments (2)
dashboard/app/page.tsx (2)

82-91: Nice: this addresses the prior “loading banner never resets” concern.
isLoadingFresh now resets when loading becomes false (including failures).


93-109: The code at lines 93-109 correctly implements "cached-first on failure" behavior and does not require modification. The useTransactions hook only sets hasFetched=true on successful fetch (line 50), not on error (catch block at lines 63-65 does not set hasFetched). Therefore, displayStats will appropriately fall back to cachedStats when a fetch fails, rather than override cached data with zeros.

…ions in try/catch for error handling - Cache zero-transaction states (remove transactions.length check) - Improve error messages for localStorage failures - Remove emojis from UI copy for professional appearance
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Improve Overview Tab (Stats + Graph Updates)

1 participant