HR Dashboard React TypeScript Development UI/UX Automation

Building a Modern HR Dashboard: A Complete Implementation Guide

Learn how to build a comprehensive HR dashboard with React, TypeScript, and modern web technologies. Includes code snippets, best practices, and real-world examples.

By Pulsawork Team April 25, 2024

Modern HR departments need powerful, intuitive dashboards to manage employee data, track performance metrics, and make data-driven decisions. In this comprehensive guide, we’ll walk through building a complete HR dashboard using React, TypeScript, and modern web technologies.

Modern HR Dashboard Interface

Above: A modern HR dashboard showing employee metrics, department distribution, and real-time data visualization.

Understanding the Requirements

Before diving into implementation, let’s define what makes a great HR dashboard:

  • Real-time data visualization with charts and graphs
  • Employee management with search and filtering
  • Performance tracking and analytics
  • Responsive design for all devices
  • Role-based access control for security

Project Setup

Let’s start by setting up our project structure:

# Create new React TypeScript project
npx create-react-app hr-dashboard --template typescript

# Install necessary dependencies
npm install @mui/material @emotion/react @emotion/styled
npm install recharts @types/recharts
npm install react-router-dom @types/react-router-dom
npm install axios @types/axios

Core Dashboard Component

Here’s the main dashboard component structure:

// src/components/Dashboard/Dashboard.tsx
import React, { useState, useEffect } from 'react';
import { Box, Grid, Paper, Typography } from '@mui/material';
import { EmployeeCard } from './EmployeeCard';
import { MetricsPanel } from './MetricsPanel';
import { ChartPanel } from './ChartPanel';

interface DashboardProps {
  userId: string;
  role: 'admin' | 'manager' | 'hr';
}

export const Dashboard: React.FC<DashboardProps> = ({ userId, role }) => {
  const [employees, setEmployees] = useState<Employee[]>([]);
  const [metrics, setMetrics] = useState<DashboardMetrics>({});
  const [loading, setLoading] = useState(true);

  useEffect(() => {
    fetchDashboardData();
  }, [userId]);

  const fetchDashboardData = async () => {
    try {
      const [employeesData, metricsData] = await Promise.all([
        fetchEmployees(),
        fetchMetrics()
      ]);
      
      setEmployees(employeesData);
      setMetrics(metricsData);
    } catch (error) {
      console.error('Failed to fetch dashboard data:', error);
    } finally {
      setLoading(false);
    }
  };

  if (loading) {
    return <LoadingSpinner />;
  }

  return (
    <Box sx={{ flexGrow: 1, p: 3 }}>
      <Typography variant="h4" gutterBottom>
        HR Dashboard
      </Typography>
      
      <Grid container spacing={3}>
        <Grid item xs={12} md={8}>
          <MetricsPanel metrics={metrics} />
        </Grid>
        <Grid item xs={12} md={4}>
          <ChartPanel data={employees} />
        </Grid>
        <Grid item xs={12}>
          <EmployeeCard employees={employees} role={role} />
        </Grid>
      </Grid>
    </Box>
  );
};

Employee Management Component

The employee management component handles CRUD operations:

// src/components/Employee/EmployeeCard.tsx
import React, { useState } from 'react';
import {
  Card,
  CardContent,
  Typography,
  Button,
  Dialog,
  DialogTitle,
  DialogContent,
  TextField,
  Grid
} from '@mui/material';

interface Employee {
  id: string;
  name: string;
  email: string;
  department: string;
  position: string;
  hireDate: string;
  status: 'active' | 'inactive';
}

interface EmployeeCardProps {
  employees: Employee[];
  role: string;
  onEmployeeUpdate: (employee: Employee) => void;
}

export const EmployeeCard: React.FC<EmployeeCardProps> = ({
  employees,
  role,
  onEmployeeUpdate
}) => {
  const [selectedEmployee, setSelectedEmployee] = useState<Employee | null>(null);
  const [isEditDialogOpen, setIsEditDialogOpen] = useState(false);

  const handleEditEmployee = (employee: Employee) => {
    setSelectedEmployee(employee);
    setIsEditDialogOpen(true);
  };

  const handleSaveEmployee = async (updatedEmployee: Employee) => {
    try {
      await updateEmployee(updatedEmployee);
      onEmployeeUpdate(updatedEmployee);
      setIsEditDialogOpen(false);
    } catch (error) {
      console.error('Failed to update employee:', error);
    }
  };

  return (
    <Card>
      <CardContent>
        <Typography variant="h6" gutterBottom>
          Employee Management
        </Typography>
        
        <Grid container spacing={2}>
          {employees.map((employee) => (
            <Grid item xs={12} sm={6} md={4} key={employee.id}>
              <Card variant="outlined">
                <CardContent>
                  <Typography variant="h6">{employee.name}</Typography>
                  <Typography color="textSecondary">{employee.position}</Typography>
                  <Typography variant="body2">{employee.department}</Typography>
                  <Typography variant="body2">{employee.email}</Typography>
                  
                  {role === 'admin' && (
                    <Button
                      variant="outlined"
                      size="small"
                      onClick={() => handleEditEmployee(employee)}
                      sx={{ mt: 1 }}
                    >
                      Edit
                    </Button>
                  )}
                </CardContent>
              </Card>
            </Grid>
          ))}
        </Grid>

        <EditEmployeeDialog
          open={isEditDialogOpen}
          employee={selectedEmployee}
          onClose={() => setIsEditDialogOpen(false)}
          onSave={handleSaveEmployee}
        />
      </CardContent>
    </Card>
  );
};

Data Visualization with Charts

Implementing charts for better data insights:

// src/components/Charts/ChartPanel.tsx
import React from 'react';
import {
  LineChart,
  Line,
  BarChart,
  Bar,
  PieChart,
  Pie,
  Cell,
  XAxis,
  YAxis,
  CartesianGrid,
  Tooltip,
  Legend,
  ResponsiveContainer
} from 'recharts';
import { Card, CardContent, Typography, Box } from '@mui/material';

interface ChartPanelProps {
  data: any[];
}

export const ChartPanel: React.FC<ChartPanelProps> = ({ data }) => {
  const departmentData = data.reduce((acc, employee) => {
    acc[employee.department] = (acc[employee.department] || 0) + 1;
    return acc;
  }, {});

  const chartData = Object.entries(departmentData).map(([name, value]) => ({
    name,
    value
  }));

  const COLORS = ['#0088FE', '#00C49F', '#FFBB28', '#FF8042', '#8884D8'];

  return (
    <Card>
      <CardContent>
        <Typography variant="h6" gutterBottom>
          Department Distribution
        </Typography>
        
        <ResponsiveContainer width="100%" height={300}>
          <PieChart>
            <Pie
              data={chartData}
              cx="50%"
              cy="50%"
              labelLine={false}
              label={({ name, percent }) => `${name} ${(percent * 100).toFixed(0)}%`}
              outerRadius={80}
              fill="#8884d8"
              dataKey="value"
            >
              {chartData.map((entry, index) => (
                <Cell key={`cell-${index}`} fill={COLORS[index % COLORS.length]} />
              ))}
            </Pie>
            <Tooltip />
          </PieChart>
        </ResponsiveContainer>
      </CardContent>
    </Card>
  );
};

API Integration

Setting up the API layer for data management:

// src/services/api.ts
import axios from 'axios';

const API_BASE_URL = process.env.REACT_APP_API_URL || 'http://localhost:3001/api';

const api = axios.create({
  baseURL: API_BASE_URL,
  headers: {
    'Content-Type': 'application/json',
  },
});

// Request interceptor for authentication
api.interceptors.request.use((config) => {
  const token = localStorage.getItem('authToken');
  if (token) {
    config.headers.Authorization = `Bearer ${token}`;
  }
  return config;
});

// Response interceptor for error handling
api.interceptors.response.use(
  (response) => response,
  (error) => {
    if (error.response?.status === 401) {
      // Handle unauthorized access
      localStorage.removeItem('authToken');
      window.location.href = '/login';
    }
    return Promise.reject(error);
  }
);

export const employeeAPI = {
  getAll: () => api.get('/employees'),
  getById: (id: string) => api.get(`/employees/${id}`),
  create: (data: any) => api.post('/employees', data),
  update: (id: string, data: any) => api.put(`/employees/${id}`, data),
  delete: (id: string) => api.delete(`/employees/${id}`),
};

export const metricsAPI = {
  getDashboardMetrics: () => api.get('/metrics/dashboard'),
  getDepartmentMetrics: (department: string) => 
    api.get(`/metrics/department/${department}`),
  getPerformanceMetrics: (employeeId: string) => 
    api.get(`/metrics/performance/${employeeId}`),
};

export default api;

Styling and Responsive Design

Implementing responsive design with CSS-in-JS:

// src/styles/Dashboard.styles.ts
import { styled } from '@mui/material/styles';
import { Box, Card } from '@mui/material';

export const DashboardContainer = styled(Box)(({ theme }) => ({
  padding: theme.spacing(3),
  backgroundColor: theme.palette.background.default,
  minHeight: '100vh',
  
  [theme.breakpoints.down('md')]: {
    padding: theme.spacing(2),
  },
  
  [theme.breakpoints.down('sm')]: {
    padding: theme.spacing(1),
  },
}));

export const MetricCard = styled(Card)(({ theme }) => ({
  height: '100%',
  transition: 'transform 0.2s ease-in-out, box-shadow 0.2s ease-in-out',
  
  '&:hover': {
    transform: 'translateY(-2px)',
    boxShadow: theme.shadows[8],
  },
  
  '& .MuiCardContent-root': {
    padding: theme.spacing(2),
    
    [theme.breakpoints.down('sm')]: {
      padding: theme.spacing(1.5),
    },
  },
}));

export const ChartContainer = styled(Box)(({ theme }) => ({
  height: 400,
  width: '100%',
  
  [theme.breakpoints.down('md')]: {
    height: 300,
  },
  
  [theme.breakpoints.down('sm')]: {
    height: 250,
  },
}));

Performance Optimization

Implementing performance optimizations:

// src/hooks/useEmployees.ts
import { useState, useEffect, useCallback, useMemo } from 'react';
import { employeeAPI } from '../services/api';

export const useEmployees = () => {
  const [employees, setEmployees] = useState([]);
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState(null);

  const fetchEmployees = useCallback(async () => {
    try {
      setLoading(true);
      const response = await employeeAPI.getAll();
      setEmployees(response.data);
    } catch (err) {
      setError(err.message);
    } finally {
      setLoading(false);
    }
  }, []);

  const filteredEmployees = useMemo(() => {
    return employees.filter(employee => employee.status === 'active');
  }, [employees]);

  const departmentStats = useMemo(() => {
    return employees.reduce((acc, employee) => {
      const dept = employee.department;
      acc[dept] = (acc[dept] || 0) + 1;
      return acc;
    }, {});
  }, [employees]);

  useEffect(() => {
    fetchEmployees();
  }, [fetchEmployees]);

  return {
    employees: filteredEmployees,
    allEmployees: employees,
    departmentStats,
    loading,
    error,
    refetch: fetchEmployees,
  };
};

Testing Implementation

Setting up comprehensive testing:

// src/components/Dashboard/__tests__/Dashboard.test.tsx
import React from 'react';
import { render, screen, waitFor } from '@testing-library/react';
import { Dashboard } from '../Dashboard';
import { employeeAPI } from '../../../services/api';

// Mock the API
jest.mock('../../../services/api');

describe('Dashboard Component', () => {
  const mockEmployees = [
    {
      id: '1',
      name: 'John Doe',
      email: 'john@example.com',
      department: 'Engineering',
      position: 'Software Engineer',
      hireDate: '2023-01-15',
      status: 'active'
    }
  ];

  const mockMetrics = {
    totalEmployees: 150,
    activeEmployees: 145,
    departments: 8,
    turnoverRate: 3.2
  };

  beforeEach(() => {
    jest.clearAllMocks();
  });

  it('renders dashboard with employee data', async () => {
    employeeAPI.getAll.mockResolvedValue({ data: mockEmployees });
    employeeAPI.getDashboardMetrics.mockResolvedValue({ data: mockMetrics });

    render(<Dashboard userId="123" role="admin" />);

    await waitFor(() => {
      expect(screen.getByText('HR Dashboard')).toBeInTheDocument();
      expect(screen.getByText('John Doe')).toBeInTheDocument();
    });
  });

  it('shows loading state initially', () => {
    render(<Dashboard userId="123" role="admin" />);
    expect(screen.getByTestId('loading-spinner')).toBeInTheDocument();
  });

  it('handles error state', async () => {
    employeeAPI.getAll.mockRejectedValue(new Error('API Error'));

    render(<Dashboard userId="123" role="admin" />);

    await waitFor(() => {
      expect(screen.getByText(/error/i)).toBeInTheDocument();
    });
  });
});

Deployment and Production Considerations

For production deployment, consider these important aspects:

// src/config/environment.ts
export const config = {
  apiUrl: process.env.REACT_APP_API_URL,
  environment: process.env.NODE_ENV,
  version: process.env.REACT_APP_VERSION,
  
  // Feature flags
  features: {
    advancedAnalytics: process.env.REACT_APP_ENABLE_ADVANCED_ANALYTICS === 'true',
    realTimeUpdates: process.env.REACT_APP_ENABLE_REAL_TIME === 'true',
    exportFunctionality: process.env.REACT_APP_ENABLE_EXPORT === 'true',
  },
  
  // Security settings
  security: {
    sessionTimeout: parseInt(process.env.REACT_APP_SESSION_TIMEOUT || '3600'),
    maxLoginAttempts: parseInt(process.env.REACT_APP_MAX_LOGIN_ATTEMPTS || '5'),
  }
};

Best Practices and Recommendations

  1. Security First: Always implement proper authentication and authorization
  2. Performance: Use React.memo, useMemo, and useCallback for optimization
  3. Accessibility: Ensure your dashboard meets WCAG guidelines
  4. Testing: Maintain high test coverage for critical components
  5. Monitoring: Implement error tracking and performance monitoring
  6. Documentation: Keep your code well-documented for team collaboration

Conclusion

Building a modern HR dashboard requires careful planning, proper architecture, and attention to detail. By following the patterns and code examples provided in this guide, you’ll have a solid foundation for creating a powerful, scalable HR management system.

The key to success is starting with a clear understanding of your requirements, implementing proper data management, and ensuring your solution is both performant and user-friendly. Remember to iterate based on user feedback and continuously improve your dashboard based on real-world usage patterns.

For more advanced features, consider integrating with existing HR systems, implementing real-time notifications, and adding advanced analytics capabilities. The foundation we’ve built here will scale well as your requirements grow.

Related Articles

The Future of HR Automation in 2024

The Future of HR Automation in 2024

Discover how AI-powered HR automation is transforming workforce management and improving efficiency across organizations.

Pulsawork Team Jan 15, 2024