Skip to content

Instantly share code, notes, and snippets.

@switchspan
Created January 27, 2026 02:25
Show Gist options
  • Select an option

  • Save switchspan/373dd70ed194bbbfe82bd052d81aec6b to your computer and use it in GitHub Desktop.

Select an option

Save switchspan/373dd70ed194bbbfe82bd052d81aec6b to your computer and use it in GitHub Desktop.
SF-Recon Dashboard API Integration PRD

Product Requirements Document

SF-Recon Dashboard API Integration

Jira References

  • RECON-371 — Fix Dashboard API Integration (P0 BLOCKER, 8 pts)
  • RECON-472 — Replace Core Dashboard Mock Data Hook (CRITICAL, 13 pts)
  • RECON-477 — Remove Enhanced Dashboard Mock Data (5 pts)
  • RECON-465 — QA Test Report (blocked)

Version History

v1.0 - API Integration (2026-01-27)

  • Replace useMockData.ts with real API calls
  • Create 5 dashboard API endpoints
  • Add error handling and timeouts
  • Status: 📋 Planning

Overview

The SF-Recon dashboard currently displays hardcoded mock data instead of real data from the database. This is a P0 BLOCKER — the dashboard shows fake transaction counts, match rates, and metrics that don't reflect actual system state.

Current State (Broken)

  • useMockData.ts provides fake data to all 4 role-based dashboards
  • useEnhancedMockData.ts provides fake analytics/trends data
  • API endpoints either missing or non-functional
  • No error handling — page hangs indefinitely on failure
  • No request timeouts configured

Target State

  • Real-time data from PostgreSQL RDS
  • Role-based data filtering (Admin, Manager, Analyst, Underwriter)
  • Graceful error handling with user feedback
  • <2 second page load time
  • Zero mock data in production

Goals

  1. G1: Replace useMockData.ts hook with real API-backed data
  2. G2: Create 5 core dashboard API endpoints in Lambda
  3. G3: Implement proper error boundaries and timeout handling
  4. G4: Maintain <2s dashboard load time with real data
  5. G5: Support all 4 role-based dashboard views

Non-Goals

  • ❌ Changing dashboard UI/layout (existing components stay)
  • ❌ Adding new dashboard features beyond current mock data
  • ❌ WebSocket real-time updates (polling acceptable for MVP)
  • ❌ Enhanced analytics (RECON-477 — separate ticket)

User Stories

US1: View Real Transaction Metrics

As an Administrator
I want to see actual transaction counts and match rates
So that I can monitor real system performance

Acceptance Criteria:

  • Dashboard displays real transaction count from database
  • Match rate calculated from actual reconciliation data
  • Unmatched count reflects current exceptions
  • Data refreshes on page load and manual refresh

US2: Handle API Failures Gracefully

As a Dashboard user
I want to see a helpful error message when data fails to load
So that I know something is wrong and can retry

Acceptance Criteria:

  • Error boundary catches API failures
  • User-friendly error message displayed (not white screen)
  • Retry button available
  • Error logged for debugging

US3: API Request Timeout

As a Dashboard user
I want the page to stop loading after a reasonable time
So that I'm not stuck waiting forever

Acceptance Criteria:

  • API requests timeout after 30 seconds
  • Timeout triggers error state with message
  • User can retry after timeout

US4: Role-Based Data Access

As a Manager
I want to see only my team's metrics
So that I'm not overwhelmed with system-wide data

Acceptance Criteria:

  • Administrator sees system-wide metrics
  • Manager sees department/team metrics
  • Analyst sees transaction-level details for assigned accounts
  • Underwriter sees audit and compliance view
  • Role determined from Cognito JWT claims

Technical Requirements

Stack (Existing)

  • Frontend: React 18, TypeScript, Vite
  • State: React Query (TanStack Query)
  • HTTP: Axios via @/config/axios
  • Backend: AWS Lambda (Node.js)
  • Database: PostgreSQL RDS
  • Auth: AWS Cognito (JWT with tenant_id, role claims)

Files to Modify

Frontend (FE_Code/src/):

hooks/useMockData.ts          → DELETE after migration
hooks/useDashboardData.ts     → CREATE (new hook with React Query)
services/dashboardService.ts  → CREATE (API client)
pages/dashboard/Dashboard.tsx → UPDATE (use new hook)
pages/dashboard/*Dashboard.tsx → UPDATE (all 4 role dashboards)
components/dashboard/DashboardErrorBoundary.tsx → UPDATE (improve)

Backend (BE_Code/src/lambda/):

dashboard-metrics/            → CREATE new Lambda
  index.js
  package.json

API Endpoints (5 required)

Method Path Purpose
GET /api/dashboard/summary Overall reconciliation stats
GET /api/dashboard/transactions Recent transactions (paginated)
GET /api/dashboard/exceptions Active exceptions/alerts
GET /api/dashboard/trends Historical trend data for charts
GET /api/dashboard/period-status Current period reconciliation status

API Response Schemas

GET /api/dashboard/summary

interface DashboardSummary {
  transactionCount: number;
  matchedCount: number;
  unmatchedCount: number;
  pendingCount: number;
  matchRate: number;           // percentage
  totalAmount: number;         // in cents
  periodStart: string;         // ISO date
  periodEnd: string;           // ISO date
  lastUpdated: string;         // ISO timestamp
}

GET /api/dashboard/transactions

interface TransactionsResponse {
  transactions: Array<{
    id: string;
    date: string;
    description: string;
    amount: number;
    status: 'matched' | 'unmatched' | 'pending' | 'exception';
    accountId: string;
    accountName: string;
  }>;
  pagination: {
    page: number;
    pageSize: number;
    total: number;
    hasMore: boolean;
  };
}

GET /api/dashboard/exceptions

interface ExceptionsResponse {
  exceptions: Array<{
    id: string;
    type: string;
    severity: 'low' | 'medium' | 'high' | 'critical';
    description: string;
    accountId: string;
    amount: number;
    createdAt: string;
    assignedTo?: string;
  }>;
  summary: {
    total: number;
    critical: number;
    high: number;
    medium: number;
    low: number;
  };
}

GET /api/dashboard/trends

interface TrendsResponse {
  daily: Array<{
    date: string;
    transactionCount: number;
    matchRate: number;
    exceptionCount: number;
  }>;
  periodComparison: {
    current: { matchRate: number; volume: number };
    previous: { matchRate: number; volume: number };
    change: { matchRate: number; volume: number };
  };
}

GET /api/dashboard/period-status

interface PeriodStatusResponse {
  currentPeriod: {
    id: string;
    name: string;
    startDate: string;
    endDate: string;
    status: 'open' | 'in_progress' | 'pending_review' | 'closed';
    progress: number;          // 0-100 percentage
  };
  reconciliationStatus: {
    accounts: Array<{
      id: string;
      name: string;
      status: 'not_started' | 'in_progress' | 'completed' | 'exception';
      matchRate: number;
    }>;
  };
}

Database Queries

-- Dashboard Summary (materialized view for performance)
CREATE MATERIALIZED VIEW dashboard_summary AS
SELECT 
  tenant_id,
  COUNT(*) as transaction_count,
  COUNT(CASE WHEN status = 'matched' THEN 1 END) as matched_count,
  COUNT(CASE WHEN status = 'unmatched' THEN 1 END) as unmatched_count,
  COUNT(CASE WHEN status = 'pending' THEN 1 END) as pending_count,
  ROUND(
    COUNT(CASE WHEN status = 'matched' THEN 1 END)::numeric / 
    NULLIF(COUNT(*), 0) * 100, 2
  ) as match_rate,
  SUM(amount) as total_amount,
  MIN(transaction_date) as period_start,
  MAX(transaction_date) as period_end,
  NOW() as last_updated
FROM transactions
WHERE transaction_date >= CURRENT_DATE - INTERVAL '30 days'
GROUP BY tenant_id;

-- Refresh strategy: every 5 minutes via scheduled Lambda
REFRESH MATERIALIZED VIEW CONCURRENTLY dashboard_summary;

Frontend Hook Implementation

// hooks/useDashboardData.ts
import { useQuery } from '@tanstack/react-query';
import { dashboardService } from '@/services/dashboardService';

export function useDashboardData(options?: { enabled?: boolean }) {
  const summary = useQuery({
    queryKey: ['dashboard', 'summary'],
    queryFn: () => dashboardService.getSummary(),
    staleTime: 30_000,        // 30 seconds
    retry: 2,
    enabled: options?.enabled ?? true,
  });

  const transactions = useQuery({
    queryKey: ['dashboard', 'transactions'],
    queryFn: () => dashboardService.getTransactions(),
    staleTime: 30_000,
    retry: 2,
    enabled: options?.enabled ?? true,
  });

  const exceptions = useQuery({
    queryKey: ['dashboard', 'exceptions'],
    queryFn: () => dashboardService.getExceptions(),
    staleTime: 30_000,
    retry: 2,
    enabled: options?.enabled ?? true,
  });

  return {
    summary: summary.data,
    transactions: transactions.data,
    exceptions: exceptions.data,
    isLoading: summary.isLoading || transactions.isLoading,
    isError: summary.isError || transactions.isError || exceptions.isError,
    error: summary.error || transactions.error || exceptions.error,
    refetch: () => {
      summary.refetch();
      transactions.refetch();
      exceptions.refetch();
    },
  };
}

Error Boundary Update

// components/dashboard/DashboardErrorBoundary.tsx
export function DashboardErrorBoundary({ children }: { children: React.ReactNode }) {
  return (
    <ErrorBoundary
      fallback={({ error, resetErrorBoundary }) => (
        <div className="flex flex-col items-center justify-center h-64 p-6">
          <AlertCircle className="h-12 w-12 text-red-500 mb-4" />
          <h3 className="text-lg font-semibold text-gray-900 mb-2">
            Failed to load dashboard
          </h3>
          <p className="text-gray-600 mb-4 text-center max-w-md">
            {error?.message || 'An unexpected error occurred while loading the dashboard.'}
          </p>
          <Button onClick={resetErrorBoundary}>
            <RefreshCw className="h-4 w-4 mr-2" />
            Try Again
          </Button>
        </div>
      )}
    >
      {children}
    </ErrorBoundary>
  );
}

Axios Timeout Configuration

// services/dashboardService.ts
import { api } from '@/config/axios';

const DASHBOARD_TIMEOUT = 30_000; // 30 seconds

class DashboardService {
  private baseUrl = '/api/dashboard';

  async getSummary(): Promise<DashboardSummary> {
    const response = await api.get(`${this.baseUrl}/summary`, {
      timeout: DASHBOARD_TIMEOUT,
    });
    return response.data;
  }
  
  // ... other methods
}

export const dashboardService = new DashboardService();

Implementation Phases

Phase 1: Backend APIs (RECON-371)

  • Create dashboard-metrics Lambda function
  • Implement /api/dashboard/summary endpoint
  • Implement /api/dashboard/transactions endpoint
  • Implement /api/dashboard/exceptions endpoint
  • Create database materialized view
  • Add role-based data filtering
  • Deploy to dev environment
  • Test with Postman/curl

Phase 2: Frontend Integration (RECON-472)

  • Create dashboardService.ts with API client
  • Create useDashboardData.ts hook with React Query
  • Update Dashboard.tsx to use new hook
  • Update AdministratorDashboard.tsx
  • Update ManagerDashboard.tsx
  • Update AnalystDashboard.tsx
  • Update UnderwriterDashboard.tsx
  • Configure 30s request timeout

Phase 3: Error Handling

  • Improve DashboardErrorBoundary.tsx
  • Add loading skeleton states
  • Add retry logic with exponential backoff
  • Add toast notifications for errors
  • Handle network timeout gracefully

Phase 4: Cleanup & Testing

  • Delete useMockData.ts
  • Delete useEnhancedMockData.ts (if not needed for RECON-477)
  • Update integration tests
  • Performance test (<2s load time)
  • QA sign-off (re-run RECON-465 tests)

Success Criteria

Metric Target Measurement
Page Load Time < 2 seconds Lighthouse/DevTools
API Response Time < 500ms CloudWatch metrics
Error Rate < 1% CloudWatch alarms
Mock Data Files 0 Code review
QA Test Pass Rate 100% RECON-465 re-test

QA Test Cases (from RECON-465)

ID Test Expected Result
TC_001 Dashboard loads real data Transaction counts from DB displayed
TC_002 Loading state shown Spinner while fetching
TC_003 Error message on API fail User-friendly error + retry button
TC_004 No console errors Zero JS errors on load
TC_005 Unmatched count correct Matches database query
TC_006 Error boundary prevents crash Graceful fallback UI
TC_007 Data refetches on focus React Query window focus
TC_008 Empty dataset handled "No data" message shown
TC_009 Timeout handling Error after 30s, not infinite
TC_010 Status categories correct All counts accurate

Dependencies

  • AWS Lambda runtime configured
  • PostgreSQL RDS accessible from Lambda
  • Cognito JWT tokens include tenant_id and role claims
  • API Gateway routes configured for /api/dashboard/*

Risks & Mitigations

Risk Impact Mitigation
Slow queries on large datasets Page timeout Materialized views, pagination
Lambda cold starts Slow first load Provisioned concurrency
Missing role in JWT Auth failures Fallback to 'analyst' role

References

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment