Skip to main content

Dashboard Components

The Dashboard Components provide statistical displays, metric cards, and dashboard layouts for the WorkPayCore Frontend application. These components handle key performance indicators, summary statistics, and data visualization with consistent styling and interactive behaviors.

Overview

This document covers dashboard-related components that display statistics, metrics, and summary information throughout the application with WorkPay's branded styling and interaction patterns.

Components Overview

Core Dashboard Components

Dashboard Layout Components

Dashboard Patterns


TopStatCard

The primary statistics card component for displaying key metrics with icons, hover effects, and navigation links.

Component Location

import TopStatCard from 'components/Dashboard/TopStatCard';
// or
import TopStatCard from 'containers/MainDashboard/TopStatCard';

Props

PropTypeRequiredDefaultDescription
statobject-Statistics data object
metaobject-Metadata including id and links
isPercentageboolean-falseWhether to display percentage symbol

TypeScript Interface

interface StatData {
total: number;
isLoading?: boolean;
}

interface MetaData {
id: string;
statFor: string;
linkTo?: string;
linkLable?: string;
isLoading?: boolean;
}

interface TopStatCardProps {
stat: StatData;
meta: MetaData;
isPercentage?: boolean;
}

Supported Stat Types

The component supports different statistics with specific icons:

  • employees - User icon for employee counts
  • leaves - Overtime icon for leave requests
  • salary_advances - Salary advance icon for advance requests
  • employee_turnover - User minus icon for turnover rates

Usage Examples

Basic Employee Stats Card

import TopStatCard from 'components/Dashboard/TopStatCard';

function EmployeeStatsCard() {
const employeeStats = {
total: 150,
};

const metaData = {
id: 'employees',
statFor: 'Total Employees',
linkTo: '/employees',
linkLable: 'View All Employees',
isLoading: false,
};

return <TopStatCard stat={employeeStats} meta={metaData} />;
}

Loading State Card

function LoadingStatsCard() {
const metaData = {
id: 'leaves',
statFor: 'Leave Requests',
isLoading: true,
};

return <TopStatCard stat={{ total: 0 }} meta={metaData} />;
}

Percentage Display Card

function TurnoverStatsCard() {
const turnoverStats = {
total: 15.5,
};

const metaData = {
id: 'employee_turnover',
statFor: 'Employee Turnover',
linkTo: '/reports/turnover',
linkLable: 'View Report',
};

return <TopStatCard stat={turnoverStats} meta={metaData} isPercentage />;
}
function SimpleStatsCard() {
const simpleStats = {
total: 42,
};

const metaData = {
id: 'employees',
statFor: 'Active Projects',
// No linkTo provided
};

return <TopStatCard stat={simpleStats} meta={metaData} />;
}

Interactive Features

  • Hover Effects: Background gradient and icon color changes
  • Navigation: Click-through to detailed views
  • Icons: Contextual icons based on stat type
  • Loading States: Skeleton loading animation

StatCardBluePrint

A blueprint component for creating custom statistics cards with flexible header, body, and footer sections.

Component Location

import StatCardBluePrint, {
StatCardHeader,
StatCardBody,
StatCardFooter,
} from 'components/Dashboard/StatCardBluePrint';
// or
import StatCardBluePrint, {
StatCardHeader,
StatCardBody,
StatCardFooter,
} from 'containers/MainDashboard/StatCardBlueprints';

Props

PropTypeRequiredDefaultDescription
headerReactNode--Header section content
bodyReactNode--Body section content
footerReactNode--Footer section content
noFooterboolean-falseHide footer section
topDividerboolean-falseShow divider after header

Usage Examples

Complete Custom Card

import StatCardBluePrint, {
StatCardHeader,
StatCardBody,
StatCardFooter,
} from 'components/Dashboard/StatCardBluePrint';
import { Text, VStack, Button } from '@chakra-ui/react';

function CustomMetricsCard() {
const header = (
<StatCardHeader heading='Monthly Overview' subHeading='September 2024' />
);

const body = (
<StatCardBody px={5} py={2} w='full' justify='center'>
<VStack spacing={4} align='center'>
<Text fontSize='3xl' color='green' fontWeight='bold'>
85%
</Text>
<Text fontSize='md' textAlign='center'>
Employee Satisfaction Score
</Text>
<Text fontSize='sm' color='gray.600' textAlign='center'>
Based on latest quarterly survey
</Text>
</VStack>
</StatCardBody>
);

const footer = (
&lt;StatCardFooter&gt;
<Text fontSize='sm' color='green'>
+5% from last month
</Text>
</StatCardFooter>
);

return <StatCardBluePrint header={header} body={body} footer={footer} />;
}
function SimpleCard() {
const header = <StatCardHeader heading='Recent Activity' />;

const body = (
<StatCardBody p={4}>
<VStack spacing={3} align='stretch'>
&lt;Text&gt;New employee onboarded</Text>
&lt;Text&gt;Payroll processed</Text>
&lt;Text&gt;Leave request approved</Text>
</VStack>
</StatCardBody>
);

return <StatCardBluePrint header={header} body={body} noFooter />;
}

Chart Integration Card

import { Chart } from 'react-google-charts';

function PayrollChartCard({ payrollData }) {
const header = <StatCardHeader heading='Payroll Summary' />;

const body = (
<StatCardBody px={5} py={2} w='full' justify='flex-start'>
<VStack w='full' align='flex-start'>
<HStack w='full' justify='space-between'>
&lt;HStack&gt;
<Text color='onyx' fontWeight='bold' fontSize='lg'>
KES 2,500,000
</Text>
<Text color='#387E1B' bg='#e7f1e3' fontSize='sm' p={1}>
+12%
</Text>
</HStack>
</HStack>
<HStack w='full' h='full' justify='center' align='center'>
{payrollData?.length > 0 ? (
<Chart
chartType='LineChart'
width='100%'
height='300px'
data={payrollData}
options={chartOptions}
/>
) : (
<NotDataFound message='No payroll data available' />
)}
</HStack>
</VStack>
</StatCardBody>
);

return <StatCardBluePrint header={header} body={body} topDivider />;
}

TopPortalStatCard

A specialized statistics card designed for the employee portal with button-style interactions.

Component Location

import TopPortalStatCard from 'containers/MainDashboard/TopPortalStatCard';

Props

PropTypeRequiredDefaultDescription
statobject-Statistics data
metaobject-Metadata and styling

Usage Examples

Portal Quick Action Card

import TopPortalStatCard from 'containers/MainDashboard/TopPortalStatCard';

function LeaveRequestCard() {
const metaData = {
statFor: 'Leave Requests',
description: 'Submit and track your leave requests',
linkTo: '/leaves/new',
linkLable: 'Request Leave',
};

return <TopPortalStatCard stat={{ isLoading: false }} meta={metaData} />;
}

StatCardHeader

Header component for statistics cards with title and subtitle support.

Component Location

import { StatCardHeader } from 'components/Dashboard/StatCardBluePrint';

Props

PropTypeRequiredDefaultDescription
headingstring-Main heading text
subHeadingstring--Subtitle text
childrenReactNode--Additional content

Usage Examples

Basic Header

<StatCardHeader heading='Employee Statistics' />

Header with Subtitle

<StatCardHeader heading='Monthly Report' subHeading='September 2024' />

Header with Actions

<StatCardHeader heading='Recent Activity'>
<Button variant='ghost' size='sm'>
View All
</Button>
</StatCardHeader>

StatCardBody

Body component for statistics cards with flexible content layout.

Component Location

import { StatCardBody } from 'components/Dashboard/StatCardBluePrint';

Props

PropTypeRequiredDefaultDescription
childrenReactNode-Body content

Usage Examples

Standard Body

<StatCardBody p={4}>
<VStack spacing={4}>
<Text fontSize='2xl'>150</Text>
&lt;Text&gt;Active Employees</Text>
</VStack>
</StatCardBody>

Chart Body

<StatCardBody px={5} py={2} w='full' justify='center'>
<Chart
chartType='PieChart'
data={chartData}
options={chartOptions}
width='100%'
height='300px'
/>
</StatCardBody>

StatCardFooter

Footer component for statistics cards with centered content and styling.

Component Location

import { StatCardFooter } from 'components/Dashboard/StatCardBluePrint';

Props

PropTypeRequiredDefaultDescription
childrenReactNode-Footer content

Usage Examples

&lt;StatCardFooter&gt;
<Text fontSize='sm' color='green'>
+5% from last month
</Text>
</StatCardFooter>
&lt;StatCardFooter&gt;
<HStack spacing={4}>
<Text fontSize='sm' color='gray.600'>
Last updated: 2 hours ago
</Text>
<Button variant='link' size='sm' color='green'>
Refresh
</Button>
</HStack>
</StatCardFooter>

Dashboard Patterns

Admin Dashboard

Main administrative dashboard with comprehensive statistics and navigation.

import { Grid, GridItem, Stack } from '@chakra-ui/react';
import { WPTabbedPage } from 'components/WPagesTemplates';
import TopStatCard from 'components/Dashboard/TopStatCard';

function AdminDashboard() {
const activeEmployees = useActiveEmployeeCountRequest();
const leaveRequestCount = useLeaveRequestsCount();
const salaryAdvanceRequestsCount = useSalaryAdvanceRequestsCount();
const employeeTurnOver = useEmployeeTurnOver();

return (
<WPTabbedPage pageTitle='Dashboard' overflow='scroll'>
<Stack spacing={4}>
{/* Top Statistics Row */}
<Grid
templateColumns={{ base: 'repeat(1, 1fr)', lg: 'repeat(4, 1fr)' }}
gap={4}
>
<GridItem w='full' h='full' colSpan={1}>
<TopStatCard
stat={activeEmployees}
meta={{
id: 'employees',
statFor: 'active employees',
linkLable: 'View employees',
linkTo: '/employees',
}}
/>
</GridItem>

<GridItem w='full' h='full' colSpan={1}>
<TopStatCard
stat={leaveRequestCount}
meta={{
id: 'leaves',
statFor: 'pending leaves',
linkLable: 'View leaves',
linkTo: '/leaves',
}}
/>
</GridItem>

<GridItem w='full' h='full' colSpan={1}>
<TopStatCard
stat={salaryAdvanceRequestsCount}
meta={{
id: 'salary_advances',
statFor: 'salary advances',
linkLable: 'View advances',
linkTo: '/salary-advance',
}}
/>
</GridItem>

<GridItem w='full' h='full' colSpan={1}>
<TopStatCard
stat={employeeTurnOver}
meta={{
id: 'employee_turnover',
statFor: 'employee turnover',
linkLable: 'Go to exits',
linkTo: '/exits',
}}
isPercentage
/>
</GridItem>
</Grid>

{/* Secondary Content Row */}
<Grid
templateColumns={{ base: 'repeat(1, 1fr)', lg: 'repeat(4, 1fr)' }}
gap={4}
>
<GridItem w='full' h='full' colSpan={3}>
<RecentTransactions />
</GridItem>
<GridItem w='full' h='full' colSpan={1}>
<UpcomingEvents />
</GridItem>
</Grid>

{/* Charts and Analytics Row */}
<Stack
w='full'
spacing={4}
h='400px'
direction={{ base: 'column', lg: 'row' }}
>
<RecentLeaveRequestsStats />
<PastPayrollsStats />
</Stack>
</Stack>
</WPTabbedPage>
);
}

Portal Dashboard

Employee portal dashboard with feature-based navigation cards.

function PortalDashboard() {
const { currentUser } = useCurrentUser();
const { data: features, isLoading: isCompanyFeaturesLoading } =
useCompanyEnabledFeatures();

const carouselData = useDashboardContent(currentUser);

return (
<WPTabbedPage pageTitle='Home'>
<Stack spacing={8} direction={{ base: 'column', lg: 'row' }}>
{/* Main Content */}
<Box flex='3' width='full'>
<VStack align='stretch' width='full' spacing={0}>
{/* Hero Carousel */}
<Stack mt='8px !important'>
<HomeCarousel carouselData={carouselData} />
</Stack>

{/* Feature Cards */}
{isCompanyFeaturesLoading ? (
<SettingsSkeleton />
) : (
<>
<Text
mt='72px !important'
letterSpacing={-0.16}
fontSize='lg'
fontWeight='500'
>
For your Company
</Text>
<Grid
mt='8px !important'
templateRows='repeat(2, 1fr)'
templateColumns='repeat(2, 1fr)'
gap={6}
>
{features?.map((feature, idx) =>
idx <= 3 ? (
<GridItem
key={feature?.id}
height='214px'
rowSpan={1}
colSpan={{ base: 2, md: 1 }}
>
<HomeCard feature={feature} />
</GridItem>
) : null,
)}
</Grid>
</>
)}
</VStack>
</Box>

{/* Sidebar */}
<Stack flex='2' marginTop='80px !important' spacing={6}>
<EventsCarousel />
<ClientOnboardingProgress />
</Stack>
</Stack>
</WPTabbedPage>
);
}

Module Dashboards

Specialized dashboards for specific modules like Performance Management.

function PerformanceManagementDashboard() {
const allEmployeeKpis = useEmployeeKpiStats();
const departmentKpis = useDepartmentKpiStats();
const allAppraisals = useEmployeeAppraisalStats();
const employeesInPip = useAllPipStats();

return (
<WPTabbedPage pageTitle='Performance Management Dashboard'>
{/* KPI Statistics */}
<Grid
templateColumns={{ base: 'repeat(1, 1fr)', lg: 'repeat(8, 1fr)' }}
gap={4}
maxH={{ base: 'fit-content', lg: '470px' }}
>
<GridItem w='full' h='full' colSpan={2}>
<TopStatCard
stat={allEmployeeKpis?.data?.data?.data}
meta={{
id: 'employees',
statFor: 'total employee KPIs',
isLoading: allEmployeeKpis?.isLoading,
linkLable: 'Go-to employee KPIs',
linkTo: '/performance-management/employee-kpi',
}}
/>
</GridItem>

<GridItem w='full' h='full' colSpan={2}>
<TopStatCard
stat={departmentKpis?.data?.data?.data}
meta={{
id: 'leaves',
statFor: 'Total Department KPIs',
isLoading: departmentKpis?.isLoading,
linkLable: 'View Department KPIs',
linkTo: '/performance-management/department-kpi',
}}
/>
</GridItem>

<GridItem w='full' h='full' colSpan={2}>
<TopStatCard
stat={allAppraisals?.data?.data?.data}
meta={{
id: 'salary_advances',
statFor: 'Submitted Appraisals',
isLoading: allAppraisals?.isLoading,
linkLable: 'View Appraisals',
linkTo: '/performance-management/employee-appraisal',
}}
/>
</GridItem>

<GridItem w='full' h='full' colSpan={2}>
<TopStatCard
stat={employeesInPip?.data?.data?.data}
meta={{
id: 'leaves',
statFor: 'Employees under PIP',
linkLable: 'Go-to PIPs',
isLoading: employeesInPip?.isLoading,
linkTo: '/performance-management/pip',
}}
/>
</GridItem>
</Grid>

{/* Department KPI Stats */}
<Grid
templateColumns={{ base: 'repeat(1, 1fr)', lg: 'repeat(4, 1fr)' }}
gap={4}
>
<GridItem w='full' h='full' colSpan={4}>
<DepartmentKpiStats />
</GridItem>
</Grid>

{/* Employee KPI Stats */}
<Grid
templateColumns={{ base: 'repeat(1, 1fr)', lg: 'repeat(4, 1fr)' }}
gap={4}
>
<GridItem w='full' h='full' colSpan={4}>
<EmployeeKpiStats />
</GridItem>
</Grid>
</WPTabbedPage>
);
}

Time Attendance Dashboard

Dashboard for time attendance monitoring with specialized metrics.

function TimeAttendanceDashboard() {
const [branch, setBranch] = useState();
const [selectedIsHeadOffice, setSelectedIsHeadOffice] = useState(true);

return (
<WPTabbedPage pageTitle='Time Attendance Dashboard'>
{/* Filters */}
<HStack spacing={4} mb={6}>
<BranchFilter
value={branch}
onChange={setBranch}
selectedIsHeadOffice={selectedIsHeadOffice}
setSelectedIsHeadOffice={setSelectedIsHeadOffice}
/>
</HStack>

{/* Summary Breakdown */}
<Grid
templateColumns={{ base: 'repeat(1, 1fr)', lg: 'repeat(1, 1fr)' }}
gap={4}
mb={6}
>
<GridItem w='full' h='full' colSpan={1}>
<TotalSummaryBreakdown
branch_id={branch?.value}
selectedIsHeadOffice={selectedIsHeadOffice}
/>
</GridItem>
</Grid>

{/* Attendance and Shifts */}
<Grid
templateColumns={{ base: 'repeat(1, 1fr)', lg: 'repeat(4, 1fr)' }}
gap={4}
>
<GridItem w='full' h='full' colSpan={2}>
<AttendanceStats branch_id={branch?.value} date={currentDate} />
</GridItem>

<GridItem w='full' h='full' colSpan={2}>
<ShiftStats
branch_id={branch?.value}
records_count={20}
date={currentDate}
/>
</GridItem>
</Grid>
</WPTabbedPage>
);
}

Best Practices

Card Design

  1. Consistent Sizing

    • Use uniform card heights within rows
    • Maintain consistent padding and spacing
    • Ensure responsive behavior across devices
  2. Visual Hierarchy

    • Place most important metrics prominently
    • Use color coding for different metric types
    • Provide clear navigation paths
  3. Loading States

    • Implement skeleton loading for all cards
    • Handle error states gracefully
    • Provide retry mechanisms when appropriate

Data Display

  1. Number Formatting

    // Format large numbers
    const formatNumber = num => {
    if (num >= 1000000) return `${(num / 1000000).toFixed(1)}M`;
    if (num >= 1000) return `${(num / 1000).toFixed(1)}K`;
    return num.toString();
    };
  2. Percentage Display

    // Consistent percentage formatting
    const formatPercentage = (value, decimals = 1) => {
    return `${value.toFixed(decimals)}%`;
    };
  3. Status Indicators

    // Color-coded status indicators
    const getStatusColor = change => {
    if (change > 0) return 'green';
    if (change < 0) return 'red';
    return 'gray';
    };

Performance

  1. Data Fetching

    • Use React Query for efficient data management
    • Implement proper caching strategies
    • Handle concurrent requests appropriately
  2. Rendering Optimization

    • Memoize expensive calculations
    • Use React.memo for stable components
    • Implement proper dependency arrays
  3. Real-time Updates

    // Implement real-time updates for critical metrics
    const useRealTimeStats = (endpoint, interval = 30000) => {
    return useQuery(['realtime-stats', endpoint], () => fetchStats(endpoint), {
    refetchInterval: interval,
    refetchIntervalInBackground: true,
    });
    };

Accessibility

  1. Screen Reader Support

    • Provide meaningful aria-labels
    • Use semantic HTML structure
    • Announce changes in dynamic content
  2. Keyboard Navigation

    • Ensure all interactive elements are keyboard accessible
    • Implement proper focus management
    • Provide keyboard shortcuts for common actions
  3. Color Accessibility

    • Ensure sufficient color contrast
    • Don't rely solely on color for information
    • Support high contrast modes

Testing

Unit Tests

import { render, screen } from '@testing-library/react';
import TopStatCard from 'components/Dashboard/TopStatCard';

describe('TopStatCard', () => {
it('renders stat data correctly', () => {
const stat = { total: 150 };
const meta = {
id: 'employees',
statFor: 'Total Employees',
};

render(<TopStatCard stat={stat} meta={meta} />);

expect(screen.getByText('150')).toBeInTheDocument();
expect(screen.getByText('Total Employees')).toBeInTheDocument();
});

it('displays percentage when specified', () => {
const stat = { total: 15.5 };
const meta = {
id: 'employee_turnover',
statFor: 'Employee Turnover',
};

render(<TopStatCard stat={stat} meta={meta} isPercentage />);

expect(screen.getByText('15.5%')).toBeInTheDocument();
});

it('shows loading state', () => {
const stat = { total: 0 };
const meta = {
id: 'employees',
statFor: 'Total Employees',
isLoading: true,
};

render(<TopStatCard stat={stat} meta={meta} />);

expect(screen.getByTestId('skeleton')).toBeInTheDocument();
});

it('renders navigation link when provided', () => {
const stat = { total: 150 };
const meta = {
id: 'employees',
statFor: 'Total Employees',
linkTo: '/employees',
linkLable: 'View All',
};

render(<TopStatCard stat={stat} meta={meta} />);

const link = screen.getByRole('link', { name: /view all/i });
expect(link).toHaveAttribute('href', '/employees');
});
});

Integration Tests

describe('AdminDashboard', () => {
it('renders all stat cards', async () => {
render(<AdminDashboard />);

await waitFor(() => {
expect(screen.getByText('active employees')).toBeInTheDocument();
expect(screen.getByText('pending leaves')).toBeInTheDocument();
expect(screen.getByText('salary advances')).toBeInTheDocument();
expect(screen.getByText('employee turnover')).toBeInTheDocument();
});
});

it('handles loading states correctly', () => {
// Mock loading state
render(<AdminDashboard />);

expect(screen.getAllByTestId('skeleton')).toHaveLength(4);
});

it('navigates to correct pages on card click', async () => {
const { user } = render(<AdminDashboard />);

await waitFor(() => {
const employeeLink = screen.getByText('View employees');
expect(employeeLink).toBeInTheDocument();
});

await user.click(screen.getByText('View employees'));

expect(mockNavigate).toHaveBeenCalledWith('/employees');
});
});

Migration Guide

From Legacy Dashboard Components

  1. Update Import Paths

    // Old
    import StatCard from 'components/OldStatCard';

    // New
    import TopStatCard from 'components/Dashboard/TopStatCard';
  2. Update Props Structure

    // Old
    <StatCard
    title="Total Employees"
    value={150}
    link="/employees"
    />

    // New
    <TopStatCard
    stat={{ total: 150 }}
    meta={{
    id: 'employees',
    statFor: 'Total Employees',
    linkTo: '/employees',
    linkLable: 'View All',
    }}
    />
  3. Update Loading States

    // Old
    <StatCard isLoading={true} />

    // New
    <TopStatCard
    stat={{ total: 0 }}
    meta={{
    id: 'employees',
    statFor: 'Total Employees',
    isLoading: true,
    }}
    />

CSS to Chakra UI Migration

// Old CSS-based styling
.stat-card {
background: white;
border-radius: 12px;
padding: 16px;
box-shadow: 0 4px 12px rgba(0,0,0,0.1);
}

// New Chakra UI styling
<StatCardBluePrint
bg="white"
borderRadius="2xl"
p={4}
boxShadow="lg"
>
{/* Content */}
</StatCardBluePrint>

Advanced Usage

Custom Dashboard Layout

function CustomDashboardLayout({ children, sidebar }) {
return (
<WPTabbedPage pageTitle='Custom Dashboard'>
<Grid templateColumns='1fr 300px' gap={6}>
{/* Main Content */}
&lt;GridItem&gt;
<Stack spacing={6}>{children}</Stack>
</GridItem>

{/* Sidebar */}
&lt;GridItem&gt;
<Stack spacing={4}>{sidebar}</Stack>
</GridItem>
</Grid>
</WPTabbedPage>
);
}

Dynamic Stat Card Configuration

const statCardConfigs = [
{
key: 'employees',
title: 'Active Employees',
icon: 'employees',
link: '/employees',
permission: 'view_employees',
},
{
key: 'leaves',
title: 'Pending Leaves',
icon: 'leaves',
link: '/leaves',
permission: 'view_leaves',
},
// ... more configs
];

function DynamicDashboard({ permissions }) {
const visibleCards = statCardConfigs.filter(config =>
permissions.includes(config.permission),
);

return (
<Grid templateColumns='repeat(auto-fit, minmax(300px, 1fr))' gap={4}>
{visibleCards.map(config => (
<TopStatCard
key={config.key}
stat={statsData[config.key]}
meta={{
id: config.key,
statFor: config.title,
linkTo: config.link,
linkLable: `View ${config.title}`,
}}
/>
))}
</Grid>
);
}

Real-time Dashboard Updates

function RealTimeDashboard() {
const stats = useRealTimeStats('/api/dashboard/stats');
const lastUpdate = useRef(Date.now());

useInterval(() => {
// Refetch data every 30 seconds
stats.refetch();
lastUpdate.current = Date.now();
}, 30000);

return (
<Stack spacing={6}>
<HStack justify='space-between' align='center'>
<Text fontSize='lg' fontWeight='bold'>
Dashboard
</Text>
<Text fontSize='sm' color='gray.600'>
Last updated: {format(lastUpdate.current, 'HH:mm:ss')}
</Text>
</HStack>

<Grid templateColumns='repeat(4, 1fr)' gap={4}>
{/* Real-time stat cards */}
</Grid>
</Stack>
);
}