- 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)
- Replace useMockData.ts with real API calls
- Create 5 dashboard API endpoints
- Add error handling and timeouts
- Status: 📋 Planning
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.
useMockData.tsprovides fake data to all 4 role-based dashboardsuseEnhancedMockData.tsprovides fake analytics/trends data- API endpoints either missing or non-functional
- No error handling — page hangs indefinitely on failure
- No request timeouts configured
- 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
- G1: Replace
useMockData.tshook with real API-backed data - G2: Create 5 core dashboard API endpoints in Lambda
- G3: Implement proper error boundaries and timeout handling
- G4: Maintain <2s dashboard load time with real data
- G5: Support all 4 role-based dashboard views
- ❌ 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)
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
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
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
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
- 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)
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
| 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 |
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
}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;
};
}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;
};
}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 };
};
}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;
}>;
};
}-- 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;// 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();
},
};
}// 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>
);
}// 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();- Create
dashboard-metricsLambda function - Implement
/api/dashboard/summaryendpoint - Implement
/api/dashboard/transactionsendpoint - Implement
/api/dashboard/exceptionsendpoint - Create database materialized view
- Add role-based data filtering
- Deploy to dev environment
- Test with Postman/curl
- Create
dashboardService.tswith API client - Create
useDashboardData.tshook with React Query - Update
Dashboard.tsxto use new hook - Update
AdministratorDashboard.tsx - Update
ManagerDashboard.tsx - Update
AnalystDashboard.tsx - Update
UnderwriterDashboard.tsx - Configure 30s request timeout
- Improve
DashboardErrorBoundary.tsx - Add loading skeleton states
- Add retry logic with exponential backoff
- Add toast notifications for errors
- Handle network timeout gracefully
- 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)
| 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 |
| 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 |
- AWS Lambda runtime configured
- PostgreSQL RDS accessible from Lambda
- Cognito JWT tokens include
tenant_idandroleclaims - API Gateway routes configured for
/api/dashboard/*
| 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 |
- RECON-371 Jira
- RECON-472 Jira
- QA Report RECON-465
- Codebase:
FE_Code/src/hooks/useMockData.ts - Codebase:
BE_Code/src/lambda/dashboard-widgets/