HR Dashboard React TypeScript Development UI/UX Automation

Budovanie moderného HR dashboardu: Kompletný implementačný sprievodca

Naučte sa, ako vytvoriť komplexný HR dashboard pomocou React, TypeScript a moderných webových technológií. Obsahuje kódové príklady, najlepšie postupy a reálne príklady.

By Pulsawork Team 25. apríla 2024

Moderné HR oddelenia potrebujú výkonné, intuitívne dashboardy na správu údajov zamestnancov, sledovanie metrík výkonnosti a rozhodovanie založené na dátach. V tomto komplexnom sprievodcovi si prejdeme vytvorenie kompletného HR dashboardu pomocou React, TypeScript a moderných webových technológií.

Moderný HR Dashboard Interface

Obrázok: Moderný HR dashboard zobrazujúci metriky zamestnancov, distribúciu oddelení a vizualizáciu dát v reálnom čase.

Pochopenie požiadaviek

Pred tým, ako sa pustíme do implementácie, definujme si, čo robí skvelý HR dashboard:

  • Vizualizácia dát v reálnom čase s grafmi a diagrammi
  • Správa zamestnancov s vyhľadávaním a filtrovaním
  • Sledovanie výkonnosti a analýzy
  • Responzívny dizajn pre všetky zariadenia
  • Kontrola prístupu na základe rolí pre bezpečnosť

Nastavenie projektu

Začnime nastavením štruktúry projektu:

# Vytvorenie nového React TypeScript projektu
npx create-react-app hr-dashboard --template typescript

# Inštalácia potrebných závislostí
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

Hlavná komponenta dashboardu

Tu je štruktúra hlavnej komponenty dashboardu:

// 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>
  );
};

Komponenta správy zamestnancov

Komponenta správy zamestnancov spravuje CRUD operácie:

// 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>
          Správa zamestnancov
        </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 }}
                    >
                      Upraviť
                    </Button>
                  )}
                </CardContent>
              </Card>
            </Grid>
          ))}
        </Grid>

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

Vizualizácia dát pomocou grafov

Implementácia grafov pre lepšie pochopenie dát:

// 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>
          Distribúcia oddelení
        </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>
  );
};

Integrácia API

Nastavenie API vrstvy pre správu dát:

// 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 pre autentifikáciu
api.interceptors.request.use((config) => {
  const token = localStorage.getItem('authToken');
  if (token) {
    config.headers.Authorization = `Bearer ${token}`;
  }
  return config;
});

// Response interceptor pre spracovanie chýb
api.interceptors.response.use(
  (response) => response,
  (error) => {
    if (error.response?.status === 401) {
      // Spracovanie neoprávneného prístupu
      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;

Štýlovanie a responzívny dizajn

Implementácia responzívneho dizajnu s 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,
  },
}));

Optimalizácia výkonu

Implementácia optimalizácií výkonu:

// 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,
  };
};

Implementácia testovania

Nastavenie komplexného testovania:

// 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 API
jest.mock('../../../services/api');

describe('Dashboard Component', () => {
  const mockEmployees = [
    {
      id: '1',
      name: 'Ján Novák',
      email: 'jan@example.com',
      department: 'Inžinierstvo',
      position: 'Softvérový inžinier',
      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('Ján Novák')).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();
    });
  });
});

Nasadenie a produkčné úvahy

Pre produkčné nasadenie zvážte tieto dôležité aspekty:

// 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',
  },
  
  // Bezpečnostné nastavenia
  security: {
    sessionTimeout: parseInt(process.env.REACT_APP_SESSION_TIMEOUT || '3600'),
    maxLoginAttempts: parseInt(process.env.REACT_APP_MAX_LOGIN_ATTEMPTS || '5'),
  }
};

Najlepšie postupy a odporúčania

  1. Bezpečnosť na prvom mieste: Vždy implementujte správnu autentifikáciu a autorizáciu
  2. Výkon: Používajte React.memo, useMemo a useCallback pre optimalizáciu
  3. Prístupnosť: Zabezpečte, aby váš dashboard spĺňal WCAG smernice
  4. Testovanie: Udržiavajte vysoké pokrytie testami pre kritické komponenty
  5. Monitoring: Implementujte sledovanie chýb a monitorovanie výkonu
  6. Dokumentácia: Udržiavajte váš kód dobre zdokumentovaný pre spoluprácu tímu

Záver

Budovanie moderného HR dashboardu vyžaduje starostlivé plánovanie, správnu architektúru a pozornosť k detailom. Podľa vzorov a kódových príkladov poskytnutých v tomto sprievodcovi budete mať solidný základ pre vytvorenie výkonného, škálovateľného systému správy HR.

Kľúčom k úspechu je začať s jasným pochopením vašich požiadaviek, implementovať správnu správu dát a zabezpečiť, aby vaše riešenie bolo výkonné a používateľsky prívetivé. Pamätajte na iteráciu na základe spätnej väzby používateľov a neustále zlepšovanie vášho dashboardu na základe reálnych vzorcov používania.

Pre pokročilejšie funkcie zvážte integráciu s existujúcimi HR systémami, implementáciu notifikácií v reálnom čase a pridanie pokročilých analytických schopností. Základ, ktorý sme tu postavili, sa bude dobre škálovať, keď budú vaše požiadavky rásť.

Súvisiace články