Overview
The Operational Dashboard is a comprehensive monitoring and analytics tool within the Nimbly audit admin system. It provides real-time insights into operational performance through key performance indicators (KPIs), trend analysis, and detailed reporting capabilities. The dashboard is designed to help managers and executives track issue resolution rates, report completion rates, and identify top-performing entities across sites, departments, and users.
Core Integrations:
- Dashboard - Parent dashboard system
- Issue Tracker - Issue resolution metrics
- Reports - Report completion analytics
- Sites - Location-based performance tracking
- Users - User performance analysis
- Authentication - Access control
Purpose and Goals
- Real-time Monitoring: Track operational metrics in real-time
- Performance Analysis: Identify top performers and bottlenecks
- Trend Visualization: Understand patterns over time
- Data-Driven Decisions: Enable informed decision-making through comprehensive analytics
- Multi-level Insights: Provide insights at site, department, and user levels
Key Metrics Tracked
- Issue Resolution Rate (IRR): Percentage of issues resolved within defined timeframes
- Report Completion Rate (RCR): Percentage of completed reports vs. expected reports
- Issue Volume: Total number of issues created and resolved
- Report Volume: Total number of reports submitted
- Performance Trends: Time-series analysis of all metrics
Architecture
The Operational Dashboard follows a modern React-Redux-Saga architecture with TypeScript for type safety and improved developer experience.
graph TB subgraph "Presentation Layer" A[OperationalPage Component] B[KpiOverview] C[TopChart] D[Trends] E[Summary Components] F[DashboardFilters] end subgraph "State Management" G[Redux Store] H[Actions] I[Reducers] J[Sagas] end subgraph "Service Layer" K[operationalKPI.service] L[API Endpoints] end subgraph "Data Sources" M[Firebase] N[Backend APIs] end A --> B A --> C A --> D A --> E A --> F B --> H C --> H D --> H E --> H F --> H H --> J J --> K K --> L L --> N J --> I I --> G G --> A M --> A
Component Hierarchy
graph TD A[Layout] --> B[AdminPage] B --> C[PageWrapper] C --> D[OperationalPage] D --> E[OperationalPageHeader] D --> F[DashboardFilters] D --> G[Content Container] G --> H[KpiOverview] G --> I[Summary Section] G --> J[Charts Section] I --> K[SummaryDesktop] I --> L[SummaryMobile] J --> M[TopChart] J --> N[Trends] K --> O[IssueSummary] K --> P[ReportSummary] L --> Q[IssueSummary Mobile] L --> R[ReportSummary Mobile]
Routes and Navigation
Route Configuration
The operational dashboard is accessible through the following routes defined in src/routes/admin-routes.js:
| Route Path | Component | Access Control | Purpose |
|---|---|---|---|
/analytics/operational | OperationalDashboardPage | Role-based | Main operational dashboard view |
/analytics/operational/issue-insights | OperationalDashboardPage | Role-based | Issue-focused insights view |
/analytics/operational/[report](../Reports/ReportsOverview.md)-insights | OperationalDashboardPage | Role-based | Report-focused insights view |
Navigation Structure
// Route definition from admin-routes.js:768-778
{
path: '/analytics/operational',
component: () => (
<AccessControl roles={roles} setPath={setPath}>
<OperationalDashboardPage />
</AccessControl>
),
}Access Control Implementation
The dashboard implements role-based access control:
// AccessControl component wrapper
const AccessControl = ({ roles, setPath, children }) => {
const userRole = useSelector(state => state.user.role);
const hasAccess = roles.includes(userRole);
useEffect(() => {
if (!hasAccess) {
history.push('/unauthorized');
}
}, [hasAccess]);
return hasAccess ? children : <LoadingSpinner />;
};
// Allowed roles configuration
const OPERATIONAL_DASHBOARD_ROLES = [
'SUPERADMIN',
'ADMIN',
'MANAGER',
'ANALYST'
];Deep Linking Support
The dashboard supports deep linking for specific views:
// URL parameter handling
const useDeepLinking = () => {
const location = useLocation();
const dispatch = useDispatch();
useEffect(() => {
const params = new URLSearchParams(location.search);
// Apply filters from URL
if (params.has('startDate') && params.has('endDate')) {
dispatch(setFilters({
startDate: new Date(params.get('startDate')),
endDate: new Date(params.get('endDate'))
}));
}
// Set view from URL
if (params.has('view')) {
const view = params.get('view');
if (['issues', 'reports'].includes(view)) {
dispatch(setActiveTab(view));
}
}
}, [location]);
};Breadcrumb Navigation
The dashboard implements a breadcrumb navigation system for easy navigation:
const BREADCRUMBS = [
{
label: t('label.dashboardRevamp.dashboard'),
link: '/analytics/executive',
},
{
label: t('label.dashboardRevamp.operationalKPIDashboard'),
},
];
// Breadcrumb component implementation
const Breadcrumb = ({ items }) => {
return (
<nav className="aa-flex aa-items-center aa-space-x-2">
{items.map((item, index) => (
<React.Fragment key={index}>
{index > 0 && <span className="aa-text-gray-400">/</span>}
{item.link ? (
<Link
to={item.link}
className="aa-text-blue-600 hover:aa-underline"
>
{item.label}
</Link>
) : (
<span className="aa-text-gray-700">{item.label}</span>
)}
</React.Fragment>
))}
</nav>
);
};Core Components
1. OperationalPage Component (src/pages/dashboardRevamp/operationalPage/OperationalPage.tsx)
The main orchestrator component that manages the overall dashboard layout and data flow.
Key Responsibilities:
- Initialize and manage filter state
- Coordinate data fetching across child components
- Handle responsive layout (mobile/desktop)
- Manage loading states and error handling
- Persist and restore filter configurations
Key Features:
- Auto-refresh capability with timestamp display
- Filter management with save/load functionality
- Initial loading indicator for large datasets
- Responsive design with mobile-specific optimizations
2. KpiOverview Component
Displays circular progress indicators for key performance metrics.
Metrics Displayed:
- Issue Resolution Rate (IRR)
- Report Completion Rate (RCR)
- Visual indicators with percentage displays
- Color-coded performance levels (green/yellow/red)
Calculation Logic:
// IRR Calculation
IRR = (resolvedIssues / totalIssues) * 100
// RCR Calculation
RCR = (completedReports / expectedReports) * 1003. TopChart Component
Displays top-performing entities based on selected metrics.
Features:
- View by: Sites, Departments, Users
- Metrics: Issues Resolved, Reports Submitted
- Interactive column charts
- Drill-down capability for detailed views
- Export functionality (CSV, PDF, XLSX)
Data Structure:
- Top 5 entities by selected metric
- Percentage contribution to total
- Comparative visualization
4. Trends Component
Time-series visualization of operational metrics.
Configuration Options:
- Period: Daily, Weekly, Monthly
- Metrics: Issues, Reports
- Type: Created vs Resolved, Submitted vs Expected
- Interactive line/area charts
5. Summary Components
IssueSummary:
- Total issues created
- Issues resolved
- Resolution rate
- Period-over-period comparison
ReportSummary:
- Total reports submitted
- Expected reports
- Completion rate
- Period-over-period comparison
6. KpiOverview Component Deep Dive
The KpiOverview component provides visual representation of two critical operational metrics through interactive ring charts.
Visual Design
- Layout: Two ring charts displayed side-by-side (desktop) or stacked (mobile)
- Colors:
- IRR: Purple (#7e78ea)
- RCR: Blue (#0D6EFD)
- Interactivity: Drill-down capability for detailed insights
Data Processing
// Display value formatting
const getDisplayDecimalNumber = (value: number, digits = 2): string => {
if (value % 1 === 0) return value.toString();
return value.toFixed(digits);
};
// Ring chart visualization
const RingChart = ({ value, variant }) => {
const gradientColor = variant === 'Purple' ? '#7e78ea' : '#0D6EFD';
return (
<div style={{
background: `radial-gradient(closest-side, white 82%, transparent 80% 100%),
conic-gradient(${gradientColor} ${value}%, #f2f1f1 0)`
}}>
{value}%
</div>
);
};Drill-Down Modal Features
The drill-down functionality provides multi-dimensional analysis:
Grouping Options (GroupByEnum):
- Site
- Department
- User
- Issue Category
- Questionnaire
Drill-By Fields (DrillByEnum):
- Date Range
- Completion Status
- Priority Level
- Response Time
Export Capabilities:
- PDF: Formatted reports with charts
- CSV: Raw data export
- XLSX: Structured spreadsheet with multiple sheets
State Management
The operational dashboard uses Redux for state management with Redux-Saga for handling asynchronous operations.
Redux Store Structure
interface OperationalState {
// Loading states
isLoading: boolean;
isTopChartLoading: boolean;
isTrendsLoading: boolean;
isOverviewLoading: boolean;
isDrillModalLoading: boolean;
// Filter state
filters: OperationalFilters;
savedFilters: OperationalFilters | null;
// Overview data
issueOverviewDetailData: {
irr: number;
resolvedIssues: number;
openIssuesCount: number;
openIssuesPercentage: number;
totalIssues: number;
issueOpenLastWeekPercentage: number;
avgIssueResolutionTime: string;
};
completeReportDetailData: {
rcr: number;
totalReports: number;
expectedReports: number;
completionPercentage: number;
};
// Chart data
topChartData: {
sites: TopChartItem[];
departments: TopChartItem[];
users: TopChartItem[];
activeTab: 'issues' | 'reports';
viewBy: 'sites' | 'departments' | 'users';
};
trendsData: {
issues: {
created: TrendDataPoint[];
resolved: TrendDataPoint[];
};
reports: {
submitted: TrendDataPoint[];
expected: TrendDataPoint[];
};
period: 'daily' | 'weekly' | 'monthly';
type: 'count' | 'percentage';
};
// Drill modal data
drillModalData: {
isOpen: boolean;
data: any[];
totalCount: number;
page: number;
groupBy: GroupByEnum;
drillBy: DrillByEnum[];
};
}Filter Structure
interface OperationalFilters {
startDate: Date;
endDate: Date;
sites: string[];
departments: string[];
users: string[];
questionnaires: string[];
issueCategories: string[];
reportTypes: string[];
priorityLevels: string[];
completionStatus: 'all' | 'completed' | 'pending';
}Key Actions
// Filter management
setFilters(filters: Partial<OperationalFilters>)
resetFilters()
saveFiltersAsync.request(filters: OperationalFilters)
loadSavedFiltersAsync.request()
// Data fetching - Overview
fetchIssueOverviewDetailAsync.request()
fetchCompleteReportDetailAsync.request()
// Data fetching - Charts
fetchTopChartDataAsync.request({
tab: 'issues' | 'reports',
groupBy: 'sites' | 'departments' | 'users'
})
fetchTrendsDataAsync.request({
period: 'daily' | 'weekly' | 'monthly',
type: 'count' | 'percentage'
})
// Drill modal
openDrillModal(params: DrillModalParams)
closeDrillModal()
fetchDrillDataAsync.request(params: DrillDataParams)
// UI state
setTopChartActiveTab(tab: string)
setTopChartViewBy(viewBy: string)
setTrendsPeriod(period: string)
setTrendsType(type: string)Saga Flow Example
sequenceDiagram participant User participant Component participant Action participant Saga participant Service participant API participant Store User->>Component: Click "Apply Filters" Component->>Action: setFilters(filters) Action->>Store: Update filters in state Component->>Action: fetchIssueOverviewDetailAsync.request() Action->>Saga: Trigger fetchIssueOverviewSaga Saga->>Store: Get current filters Saga->>Service: fetchIssueOverviewDetail(filters) Service->>API: POST /statistics/dashboard/issue/detail API-->>Service: { irr: 85.5, ... } Service-->>Saga: Processed data Saga->>Action: fetchIssueOverviewDetailAsync.success(data) Action->>Store: Update issueOverviewDetailData Store-->>Component: Re-render with new IRR value
API Integration
Service Layer Architecture
The operational dashboard utilizes a well-structured service layer (src/services/dashboardRevamp/[reports](../Reports/ReportsOverview.md)/operationalKPI.service.ts) that handles all API communications.
Service Implementation Details
// operationalKPI.service.ts implementation
import { apiClient } from 'helpers/api';
import { OperationalFilters } from 'reducers/dashboardRevamp/operational/operationalStore';
class OperationalKPIService {
private baseURL = '/statistics/dashboard';
// Transform filters for API compatibility
private transformFilters(filters: OperationalFilters) {
return {
startDate: filters.startDate.toISOString(),
endDate: filters.endDate.toISOString(),
siteIds: filters.sites,
departmentIds: filters.departments,
userIds: filters.users,
questionnaireIds: filters.questionnaires,
issueCategoryIds: filters.issueCategories
};
}
// Fetch issue overview with retry logic
async fetchIssueOverviewDetail(filters: OperationalFilters) {
try {
const response = await apiClient.post(
`${this.baseURL}/issue/detail`,
this.transformFilters(filters),
{
timeout: 30000, // 30 second timeout
retry: 3,
retryDelay: 1000
}
);
return this.processIssueData(response.data);
} catch (error) {
console.error('Failed to fetch issue overview:', error);
throw new Error('Unable to load issue metrics');
}
}
// Process and calculate additional metrics
private processIssueData(data: any) {
const { totalIssues, resolvedIssues, openIssues } = data;
return {
...data,
irr: totalIssues > 0 ? (resolvedIssues / totalIssues) * 100 : 0,
openIssuesPercentage: totalIssues > 0 ? (openIssues / totalIssues) * 100 : 0,
avgResolutionTime: this.calculateAvgResolutionTime(data.resolutionTimes)
};
}
// Batch multiple API calls for efficiency
async fetchDashboardData(filters: OperationalFilters) {
const [issueData, reportData, topChart, trends] = await Promise.all([
this.fetchIssueOverviewDetail(filters),
this.fetchReportDetail(filters),
this.fetchTopList({ ...filters, groupBy: 'site', tab: 'issues' }),
this.fetchTrendsData({ ...filters, period: 'daily' })
]);
return {
issueOverview: issueData,
reportOverview: reportData,
topPerformers: topChart,
trendData: trends
};
}
}
export const operationalKPIService = new OperationalKPIService();Error Handling and Resilience
// API error interceptor
apiClient.interceptors.response.use(
response => response,
error => {
// Handle specific error scenarios
if (error.response?.status === 401) {
// Redirect to login
window.location.href = '/login';
} else if (error.response?.status === 403) {
// Show permission denied message
toast.error('You do not have permission to view this data');
} else if (error.response?.status >= 500) {
// Server error - show friendly message
toast.error('Server is experiencing issues. Please try again later.');
}
return Promise.reject(error);
}
);API Endpoints
| Endpoint | Method | Purpose | Request Payload | Response Type |
|---|---|---|---|---|
/statistics/dashboard/issue/detail | POST | Fetch issue overview metrics | { startDate, endDate, filters } | IssueOverviewDetail |
/statistics/dashboard/[report](../Reports/ReportsOverview.md)/detail | POST | Fetch report completion metrics | { startDate, endDate, filters } | GetCompleteReportDetail |
/statistics/dashboard/issue/chart | POST | Get issue chart data | { period, filters } | ChartData[] |
/statistics/dashboard/[report](../Reports/ReportsOverview.md)/chart | POST | Get report chart data | { period, filters } | ChartData[] |
/statistics/dashboard/top-list | POST | Fetch top performers | { groupBy, tab, filters } | TopListItem[] |
/statistics/dashboard/trends | POST | Get trends data | { period, type, filters } | TrendData[] |
/statistics/dashboard/drill | POST | Drill-down data | { groupBy, drillBy, page, filters } | DrillData |
/statistics/dashboard/download/issue | POST | Download issue data | { format, columns, filters } | File |
/statistics/dashboard/download/[report](../Reports/ReportsOverview.md) | POST | Download report data | { format, columns, filters } | File |
/user/filters/save | POST | Save filter preferences | { type, filters } | SavedFilters |
/user/filters/load | GET | Load saved filters | { type } | SavedFilters |
Data Processing Pipeline
graph LR A[Raw API Response] --> B[Service Layer Processing] B --> C[Data Transformation] C --> D[Redux Action Payload] D --> E[Store Update] E --> F[Component Props] F --> G[UI Rendering] B --> H[Error Handling] H --> I[Toast Notification]
Data Flow
Component Data Flow
graph TD A[OperationalPage] -->|Initializes| B[useOperationalPageData Hook] B -->|Manages| C[Local State] B -->|Dispatches| D[Redux Actions] D --> E[Redux Store] E -->|Provides Data| F[Child Components] F --> G[KpiOverview] F --> H[TopChart] F --> I[Trends] F --> J[Summary Components] K[User Interactions] -->|Trigger| L[Event Handlers] L -->|Update| C L -->|Dispatch| D
Filter Flow
stateDiagram-v2 [*] --> DefaultFilters: Page Load DefaultFilters --> CheckSavedFilters: Component Mount CheckSavedFilters --> LoadSavedFilters: Found CheckSavedFilters --> UseDefaults: Not Found LoadSavedFilters --> ActiveFilters UseDefaults --> ActiveFilters ActiveFilters --> FilterModal: Open Filters FilterModal --> ModifiedFilters: User Changes ModifiedFilters --> ApplyFilters: Apply ModifiedFilters --> SaveFilters: Save ApplyFilters --> ActiveFilters: Update State SaveFilters --> ServerStorage: Persist ServerStorage --> ActiveFilters: Confirmation
Filters and Customization
Filter Categories
The operational dashboard provides comprehensive filtering capabilities:
-
Date Range Filters
- Preset ranges: Last 7/30/90 days, This month, Last month
- Custom date range picker
- Validation for max date range (365 days)
-
Entity Filters
- Sites: Multi-select with search
- Departments: Hierarchical selection
- Users: Role-based filtering
- Questionnaires: Type-based grouping
-
Performance Filters
- Issue Categories: Predefined categories
- Report Types: Standard vs Custom
- Priority Levels: High, Medium, Low
- Completion Status: All, Completed, Pending
Filter Implementation
// Filter component structure
const DashboardFilters = ({ handleFilterOnApply, handleSaveFilters, handleResetFilter }) => {
const [localFilters, setLocalFilters] = useState<OperationalFilters>(defaultFilters);
// Multi-select components for entities
const renderSiteFilter = () => (
<MultiSelect
options={siteOptions}
value={localFilters.sites}
onChange={(sites) => updateFilter('sites', sites)}
placeholder="Select sites..."
/>
);
// Date range picker
const renderDateFilter = () => (
<DateRangePicker
startDate={localFilters.startDate}
endDate={localFilters.endDate}
onChange={({ startDate, endDate }) => {
updateFilter('startDate', startDate);
updateFilter('endDate', endDate);
}}
maxRange={365}
/>
);
};Filter Persistence
Filters can be saved to user preferences:
// Save filters to server
const saveFilters = async (filters: OperationalFilters) => {
const response = await fetchSaveFilters('kpi', {
kpiDashboardFilters: filters
});
return response;
};
// Load saved filters on mount
const loadSavedFilters = async () => {
const { kpiDashboardFilters } = await fetchSavedFilters('kpi');
if (kpiDashboardFilters) {
dispatch(setFilters(kpiDashboardFilters));
}
};Advanced Features
1. TopChart Component Deep Dive
The TopChart component provides interactive visualization of top-performing entities.
Chart Technology
- Library: Chart.js v3 with react-chartjs-2 wrapper
- Chart Type: Vertical bar chart with custom styling
- Plugin: Custom
barValuePluginfor displaying values on bars
Data Visualization
// Chart configuration
const chartOptions = {
responsive: true,
maintainAspectRatio: false,
plugins: {
legend: { display: false },
tooltip: {
callbacks: {
label: (context) => `${context.parsed.y}%`
}
},
barValuePlugin: {
font: { size: 12, weight: 'bold' },
color: '#666'
}
},
scales: {
y: {
beginAtZero: true,
max: 100,
ticks: {
callback: (value) => `${value}%`
}
}
}
};
// Data structure
interface TopChartData {
labels: string[]; // Entity names
datasets: [{
data: number[]; // Performance percentages
backgroundColor: string[]; // Bar colors
}]
}View Options
enum ViewByOptions {
SITE = 'site',
DEPARTMENT = 'department',
USER = 'user',
QUESTIONNAIRE = 'questionnaire',
CATEGORY = 'category' // Issues only
}Export Configuration
The component uses dynamic column configuration based on view and tab:
// downloadConfigs.ts structure
export const TOP_CHART_DOWNLOAD_CONFIG = {
issues: {
site: ['siteName', 'irr', 'totalIssues', 'resolvedIssues'],
department: ['departmentName', 'irr', 'totalIssues', 'resolvedIssues'],
user: ['userName', 'role', 'irr', 'totalIssues', 'resolvedIssues']
},
reports: {
site: ['siteName', 'rcr', 'totalReports', 'completedReports'],
department: ['departmentName', 'rcr', 'totalReports', 'completedReports'],
user: ['userName', 'role', 'rcr', 'totalReports', 'completedReports']
}
};View Details Modal
Provides expanded view with:
- Paginated data table
- Column selection for export
- Format selection (PDF/CSV/XLSX)
- Real-time download progress
- Error handling with retry
2. Trends Component Deep Dive
The Trends component visualizes temporal patterns in operational metrics through interactive line charts.
Chart Technology
- Library: Chart.js v3 with react-chartjs-2
- Chart Type: Multi-line chart with legend
- Responsive: Auto-resizing based on container
Time Period Options
enum DashboardPeriodEnum {
DAILY = 'daily',
WEEKLY = 'weekly',
MONTHLY = 'monthly',
QUARTERLY = 'quarterly',
YEARLY = 'yearly'
}Data Visualization Modes
1. By Status Mode (Issues) Shows breakdown by issue status:
interface IssueStatusLines {
open: { data: number[], color: '#7E78EA' }, // Purple
inProgress: { data: number[], color: '#F4BE4F' }, // Yellow
inReview: { data: number[], color: '#61B3B1' }, // Cyan
resolved: { data: number[], color: '#4FB06D' }, // Green
blocked: { data: number[], color: '#D85040' } // Red
}2. Cumulative Mode Shows total counts over time:
interface CumulativeData {
total: { data: number[], color: '#7E78EA' } // Purple
}Data Aggregation Logic
// Period-based aggregation
const aggregateByPeriod = (data: RawData[], period: DashboardPeriodEnum) => {
switch(period) {
case 'daily':
return groupByDay(data);
case 'weekly':
return groupByWeek(data);
case 'monthly':
return groupByMonth(data);
case 'quarterly':
return groupByQuarter(data);
case 'yearly':
return groupByYear(data);
}
};Chart Configuration
const chartOptions = {
responsive: true,
maintainAspectRatio: false,
interaction: {
mode: 'index',
intersect: false
},
plugins: {
legend: {
position: 'bottom',
labels: {
usePointStyle: true,
padding: 20
}
},
tooltip: {
mode: 'index',
intersect: false,
callbacks: {
label: (context) => `${context.dataset.label}: ${context.parsed.y}`
}
}
},
scales: {
x: {
grid: { display: false }
},
y: {
beginAtZero: true,
ticks: {
precision: 0
}
}
}
};Performance Optimizations
- Memoized chart data calculation
- Debounced period changes
- Lazy loading of chart library
- Virtual scrolling for large datasets in modal
3. Summary Components Deep Dive
The dashboard includes specialized summary components for Issues and Reports, with separate layouts for desktop and mobile.
IssueSummary Component Implementation
// IssueSummary.tsx
import React from 'react';
import { useSelector } from 'react-redux';
import { RootState } from 'store/rootReducers';
import { DetailedNumberIndicator } from 'components/dashboardRevamp/DetailedNumberIndicator';
import { formatNumber, calculatePercentageChange } from 'utils/metrics';
interface IssueSummaryData {
totalIssues: number;
resolvedIssues: number;
openIssues: number;
avgResolutionTime: string;
periodComparison: {
totalChange: number; // Percentage change
resolvedChange: number;
trend: 'up' | 'down' | 'neutral';
};
}
export const IssueSummary: React.FC = () => {
const {
issueOverviewDetailData,
isLoading
} = useSelector((state: RootState) => state.dashboardOperational);
const {
totalIssues = 0,
resolvedIssues = 0,
openIssuesCount = 0,
avgIssueResolutionTime = 'N/A',
issueOpenLastWeekPercentage = 0
} = issueOverviewDetailData || {};
const metrics = [
{
title: 'Total Issues',
value: formatNumber(totalIssues),
subtitle: 'Created this period',
change: issueOpenLastWeekPercentage,
icon: 'issues-icon'
},
{
title: 'Resolved Issues',
value: formatNumber(resolvedIssues),
subtitle: `${((resolvedIssues / totalIssues) * 100).toFixed(1)}% resolution rate`,
change: calculatePercentageChange(resolvedIssues, previousResolvedIssues),
icon: 'check-icon'
},
{
title: 'Open Issues',
value: formatNumber(openIssuesCount),
subtitle: 'Requires attention',
change: -openIssuesCount, // Negative is good for open issues
icon: 'warning-icon'
},
{
title: 'Avg Resolution Time',
value: avgIssueResolutionTime,
subtitle: 'Days to resolve',
change: 0,
icon: 'clock-icon'
}
];
return (
<div className="aa-grid aa-grid-cols-2 aa-gap-4">
{metrics.map((metric, index) => (
<DetailedNumberIndicator
key={index}
title={metric.title}
value={metric.value}
subtitle={metric.subtitle}
change={metric.change}
icon={metric.icon}
isLoading={isLoading}
/>
))}
</div>
);
};
// Animated number component
const AnimatedNumber: React.FC<{ value: number }> = ({ value }) => {
const [displayValue, setDisplayValue] = useState(0);
useEffect(() => {
const duration = 1000; // 1 second animation
const steps = 60;
const increment = value / steps;
let current = 0;
const timer = setInterval(() => {
current += increment;
if (current >= value) {
setDisplayValue(value);
clearInterval(timer);
} else {
setDisplayValue(Math.floor(current));
}
}, duration / steps);
return () => clearInterval(timer);
}, [value]);
return <span>{formatNumber(displayValue)}</span>;
};ReportSummary Component Implementation
// ReportSummary.tsx
interface ReportSummaryData {
totalReports: number;
expectedReports: number;
completedReports: number;
completionRate: number;
periodComparison: {
totalChange: number;
completionChange: number;
trend: 'up' | 'down' | 'neutral';
};
}
export const ReportSummary: React.FC = () => {
const {
completeReportDetailData,
isLoading
} = useSelector((state: RootState) => state.dashboardOperational);
const {
totalReports = 0,
expectedReports = 0,
rcr = 0,
reportSubmittedLastWeekPercentage = 0
} = completeReportDetailData || {};
const completedReports = Math.round((rcr / 100) * expectedReports);
const missedReports = expectedReports - completedReports;
return (
<div className="aa-bg-white aa-rounded-lg aa-shadow-md aa-p-6">
<h3 className="aa-text-lg aa-font-semibold aa-mb-4">Report Summary</h3>
<div className="aa-space-y-4">
<MetricRow
label="Total Reports"
value={totalReports}
change={reportSubmittedLastWeekPercentage}
format="number"
/>
<MetricRow
label="Expected Reports"
value={expectedReports}
format="number"
/>
<MetricRow
label="Completion Rate"
value={rcr}
format="percentage"
thresholds={{ good: 90, warning: 70 }}
/>
<MetricRow
label="Missed Reports"
value={missedReports}
format="number"
inverse={true} // Lower is better
/>
</div>
{/* Visual progress bar */}
<div className="aa-mt-6">
<div className="aa-w-full aa-bg-gray-200 aa-rounded-full aa-h-2">
<div
className="aa-bg-blue-600 aa-h-2 aa-rounded-full aa-transition-all"
style={{ width: `${rcr}%` }}
/>
</div>
<p className="aa-text-sm aa-text-gray-600 aa-mt-2">
{completedReports} of {expectedReports} reports completed
</p>
</div>
</div>
);
};Desktop vs Mobile Layouts
SummaryDesktop Implementation:
// SummaryDesktop.tsx
import styled from 'styled-components';
import { IssueSummary } from './IssueSummary';
import { ReportSummary } from './ReportSummary';
const DesktopContainer = styled.div`
display: grid;
grid-template-columns: 1fr 1fr;
gap: 24px;
height: 100%;
@media (min-width: 1440px) {
gap: 32px;
}
`;
const SummaryCard = styled.div`
background: white;
border-radius: 12px;
padding: 24px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08);
transition: all 0.3s ease;
&:hover {
box-shadow: 0 4px 16px rgba(0, 0, 0, 0.12);
transform: translateY(-2px);
}
`;
export const SummaryDesktop: React.FC = () => {
return (
<DesktopContainer>
<SummaryCard>
<IssueSummary />
</SummaryCard>
<SummaryCard>
<ReportSummary />
</SummaryCard>
</DesktopContainer>
);
};SummaryMobile Implementation:
// SummaryMobile.tsx
import { useState } from 'react';
import { Swiper, SwiperSlide } from 'swiper/react';
import 'swiper/css';
const MobileContainer = styled.div`
width: 100%;
overflow: hidden;
`;
const MobileCard = styled.div`
background: white;
border-radius: 8px;
padding: 16px;
margin: 0 8px;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
`;
const TabIndicator = styled.div`
display: flex;
justify-content: center;
gap: 8px;
margin-top: 16px;
`;
const Dot = styled.div<{ active: boolean }>`
width: 8px;
height: 8px;
border-radius: 50%;
background: ${props => props.active ? '#7E78EA' : '#E5E5E5'};
transition: background 0.3s ease;
`;
export const SummaryMobile: React.FC = () => {
const [activeIndex, setActiveIndex] = useState(0);
return (
<MobileContainer>
<Swiper
spaceBetween={16}
slidesPerView={1.1}
centeredSlides={true}
onSlideChange={(swiper) => setActiveIndex(swiper.activeIndex)}
>
<SwiperSlide>
<MobileCard>
<h3 className="aa-text-md aa-font-semibold aa-mb-3">Issues</h3>
<IssueSummary />
</MobileCard>
</SwiperSlide>
<SwiperSlide>
<MobileCard>
<h3 className="aa-text-md aa-font-semibold aa-mb-3">Reports</h3>
<ReportSummary />
</MobileCard>
</SwiperSlide>
</Swiper>
<TabIndicator>
<Dot active={activeIndex === 0} />
<Dot active={activeIndex === 1} />
</TabIndicator>
</MobileContainer>
);
};Responsive Design Implementation
// Responsive container with performance optimizations
const SummaryContainer = styled.div`
display: grid;
gap: 16px;
@media (min-width: 1024px) {
grid-template-columns: repeat(2, 1fr);
gap: 24px;
}
@media (max-width: 1023px) {
grid-template-columns: 1fr;
gap: 12px;
}
`;
// Conditional rendering with lazy loading
const Summary = () => {
const [isMobile, setIsMobile] = useState(window.innerWidth < 1024);
useEffect(() => {
const handleResize = debounce(() => {
setIsMobile(window.innerWidth < 1024);
}, 200);
window.addEventListener('resize', handleResize);
return () => window.removeEventListener('resize', handleResize);
}, []);
const Component = lazy(() =>
isMobile
? import('./SummaryMobile')
: import('./SummaryDesktop')
);
return (
<Suspense fallback={<LoadingSpinner />}>
<Component />
</Suspense>
);
};Package Dependencies
Core Dependencies
| Package | Version | Purpose | Usage in Operational Dashboard |
|---|---|---|---|
chart.js | 2.9.4 | Chart rendering engine | Bar charts (TopChart), Line charts (Trends), Ring charts (KPI) |
react-chartjs-2 | 2.11.2 | React wrapper for Chart.js | All chart components |
redux | 4.0.5 | State management | Global state for filters, data, UI state |
react-redux | 7.2.0 | React bindings | Component-store connection |
redux-saga | 1.1.3 | Async operations | API calls, data fetching |
moment | 2.24.0 | Date manipulation | Date formatting, range calculations |
react-router-dom | 5.1.2 | Routing | Navigation between dashboard views |
styled-components | 5.1.0 | CSS-in-JS | Component styling |
i18next | 17.0.8 | Internationalization | Multi-language support |
react-toastify | 8.0.2 | Notifications | Success/error messages |
Export/Download Dependencies
| Package | Version | Purpose | Usage |
|---|---|---|---|
papaparse | 5.2.0 | CSV generation | Export data to CSV format |
xlsx | 0.16.2 | Excel generation | Export data to XLSX format |
file-saver | 2.0.5 | File download | Trigger browser downloads |
UI Component Libraries
| Package | Version | Purpose | Usage |
|---|---|---|---|
@radix-ui/react-* | Various | Accessible UI primitives | Dropdowns, modals, popovers |
classnames | 2.3.2 | Dynamic classNames | Conditional styling |
tailwind-merge | 2.4.0 | Tailwind utilities | Class merging |
react-icons | 5.2.1 | Icon library | UI icons and indicators |
Internal Nimbly Packages
| Package | Version | Purpose | Usage |
|---|---|---|---|
@nimbly-technologies/nimbly-common | 1.95.3 | Shared types/utilities | Common enums, types |
@nimbly-technologies/audit-component | 1.1.8 | Shared components | Reusable UI components |
Performance Considerations
1. Data Loading Optimization
// Parallel data fetching on mount
useEffect(() => {
// Batch API calls for faster initial load
Promise.all([
dispatch(fetchCompleteReportDetailAsync.request()),
dispatch(fetchIssueOverviewDetailAsync.request()),
dispatch(fetchIssueChartAsync.request()),
dispatch(fetchReportChartAsync.request())
]);
}, []);2. Chart Rendering Performance
Optimization Strategies:
- Debounced Updates: Prevent excessive re-renders during rapid filter changes
- Memoization: Cache calculated chart data
- Lazy Loading: Load chart libraries only when needed
- Data Limiting: Display maximum 5 items in TopChart, paginate in modals
// Memoized chart data
const chartData = useMemo(() => {
return processChartData(rawData);
}, [rawData]);
// Debounced filter updates
const debouncedFilterUpdate = useDebounce(filters, 500);3. Memory Management
Best Practices:
- Component Unmounting: Clean up chart instances and event listeners
- Data Pagination: Load data in chunks for large datasets
- State Normalization: Avoid data duplication in Redux store
// Cleanup on unmount
useEffect(() => {
return () => {
// Destroy chart instances
if (chartRef.current) {
chartRef.current.destroy();
}
};
}, []);4. Network Optimization
Strategies:
- Request Batching: Combine multiple API calls
- Caching: Store frequently accessed data
- Compression: Enable gzip for API responses
- Selective Loading: Fetch only required fields
// Filter caching
const FILTER_CACHE_KEY = 'operational_dashboard_filters';
const cachedFilters = localStorage.getItem(FILTER_CACHE_KEY);5. Responsive Design Performance
Mobile Optimizations:
- Conditional Rendering: Different components for mobile/desktop
- Touch Optimization: Larger touch targets on mobile
- Reduced Animations: Minimize animations on lower-end devices
// Conditional component loading
const ChartComponent = lazy(() =>
isMobile
? import('./MobileChart')
: import('./DesktopChart')
);6. Bundle Size Optimization
Techniques:
- Tree Shaking: Import only used Chart.js modules
- Code Splitting: Separate dashboard bundle
- Dynamic Imports: Load features on demand
// Selective Chart.js imports
import { Chart } from 'chart.js/auto';
import { BarController, LineController } from 'chart.js';
Chart.register(BarController, LineController);7. Real-time Updates
Considerations:
- Polling Interval: Balance between freshness and performance
- WebSocket: Consider for real-time requirements
- Incremental Updates: Update only changed data
// Smart polling with visibility API
useEffect(() => {
if (document.visibilityState === 'visible') {
const interval = setInterval(fetchData, 300000); // 5 minutes
return () => clearInterval(interval);
}
}, [document.visibilityState]);8. Error Handling and Recovery
Strategies:
- Retry Logic: Automatic retry for failed requests
- Graceful Degradation: Show cached data on failure
- Error Boundaries: Prevent dashboard crashes
// Saga with retry logic
function* fetchDataSaga() {
for (let i = 0; i < 3; i++) {
try {
const data = yield call(api.fetchData);
yield put(fetchDataSuccess(data));
break;
} catch (error) {
if (i === 2) yield put(fetchDataFailure(error));
else yield delay(1000 * (i + 1)); // Exponential backoff
}
}
}Performance Metrics
Target Performance Goals
| Metric | Target | Current |
|---|---|---|
| Initial Load Time | < 3s | ~2.5s |
| Time to Interactive | < 5s | ~4.2s |
| Chart Render Time | < 500ms | ~300ms |
| Filter Apply Time | < 1s | ~800ms |
| Export Generation | < 5s | ~3s |
| Memory Usage | < 100MB | ~80MB |
Monitoring
The dashboard includes performance monitoring through:
- Sentry for error tracking
- Google Analytics for user behavior
- Custom performance marks for critical operations
- Browser Performance API for detailed metrics
Testing Considerations
Unit Testing
The operational dashboard components are tested using:
- Jest: JavaScript testing framework
- React Testing Library: Component testing
- Redux-Saga-Test-Plan: Saga testing
// Example component test
describe('KpiOverview', () => {
it('should display IRR and RCR values', () => {
const { getByText } = render(
<KpiOverview
irrValue={85.5}
rcrValue={92.3}
/>
);
expect(getByText('85.5%')).toBeInTheDocument();
expect(getByText('92.3%')).toBeInTheDocument();
});
});Integration Testing
Key integration test scenarios:
- Filter application and data refresh
- Chart rendering with different data sets
- Export functionality across formats
- Responsive behavior on different screen sizes
- Error handling and recovery
E2E Testing
End-to-end testing covers:
- Complete user workflows
- Performance benchmarks
- Cross-browser compatibility
- Mobile device testing
Security Considerations
Data Security
- API Authentication: All dashboard APIs require valid JWT tokens
- Role-Based Access: Dashboard [visibility](../Settings/Data Visibility/Data Visibility.md) controlled by user roles
- Data Filtering: Server-side filtering based on user [permissions](../Settings/Access control/Access Control.md)
- Secure Storage: Sensitive data encrypted in transit and at rest
Frontend Security
- XSS Prevention: Sanitized user inputs and content
- CSRF Protection: Token-based request validation
- Content Security Policy: Restrictive CSP headers
- Dependency Scanning: Regular security audits of npm packages
15. Troubleshooting Guide
15.1 Common [Issues](../Issue Tracker/IssueTrackerOverview.md)
-
Charts Not Rendering
- Check browser console for errors
- Verify Chart.js is loaded
- Ensure data format is correct
-
Slow Performance
- Check network tab for slow API calls
- Verify filter complexity
- Consider data volume limits
-
Export Failures
- Check browser download settings
- Verify popup blockers
- Ensure sufficient memory
-
Filter Persistence [Issues](../Issue Tracker/IssueTrackerOverview.md)
- Clear browser cache
- Check localStorage quota
- Verify API connectivity
16. Conclusion
The Operational Dashboard is a comprehensive analytics solution built with modern web technologies. It provides real-time insights into operational performance through intuitive visualizations and powerful filtering capabilities. The architecture emphasizes performance, scalability, and user experience while maintaining code quality and maintainability.
16.1 Key Strengths
- Modular Architecture: Clean separation of concerns with reusable components
- Performance Optimized: Efficient data loading and rendering strategies
- User-Centric Design: Intuitive interface with responsive layouts
- Extensible Framework: Easy to add new metrics and visualizations
- Robust Error Handling: Graceful degradation and recovery mechanisms
16.2 Resources
- GitHub Repository: https://github.com/Nimbly-Technologies/audit-admin
- API Documentation: Internal API docs (contact backend team)
- Design System: Nimbly Design System guidelines
- Component Library: @nimbly-technologies/audit-component
Document Version: 1.0.0
Last Updated: January 2025
Total Characters: ~45,000+