Skip to content

Latest commit

 

History

History
3179 lines (2784 loc) · 95.6 KB

File metadata and controls

3179 lines (2784 loc) · 95.6 KB

React Fundamentals Course

Lesson 5 Lab: Employee Directory

Lab Overview

In this hands-on lab, you will build a comprehensive employee directory that demonstrates dynamic list rendering, React keys, filtering, and sorting. You'll learn how to efficiently render large datasets, implement search functionality, and optimize list performance. This lab covers the essential patterns for working with dynamic data in React applications.

Total Estimated Time: 30 minutes


Exercise 1: Project Setup and Basic List Rendering

Estimated Time: 8 minutes

In this exercise, you will create a new React project for the employee directory and set up the basic structure for rendering lists of employees. You'll learn about the map() function for rendering arrays, the importance of React keys, and basic list rendering patterns.

Step 1.1: Create a New React Project

  1. Open VS Code on your Windows virtual machine
  2. Open the integrated terminal (`Ctrl + ``)
  3. Navigate to your desired project directory:
    cd Desktop
  4. Create a new React application called "employee-directory":
    npx create-react-app employee-directory
  5. Navigate into the project directory:
    cd employee-directory
  6. Open the project in VS Code (File > Open Folder and select employee-directory)

Step 1.2: Start the Development Server

  1. Start the development server:
    npm start
  2. Verify the application loads in your browser at http://localhost:3000

Step 1.3: Create Employee Data Structure

  1. Create a data folder in the src directory
  2. Create a new file called employeeData.js in the data folder
  3. Add the following employee data:
    export const employees = [
      {
        id: 1,
        firstName: "Sarah",
        lastName: "Johnson",
        email: "sarah.johnson@company.com",
        phone: "(555) 123-4567",
        department: "Engineering",
        position: "Senior Software Engineer",
        location: "New York",
        hireDate: "2021-03-15",
        salary: 95000,
        profileImage: "👩‍💻",
        skills: ["JavaScript", "React", "Node.js", "Python"],
        status: "Active"
      },
      {
        id: 2,
        firstName: "Michael",
        lastName: "Chen",
        email: "michael.chen@company.com",
        phone: "(555) 234-5678",
        department: "Marketing",
        position: "Marketing Manager",
        location: "San Francisco",
        hireDate: "2020-07-22",
        salary: 78000,
        profileImage: "👨‍💼",
        skills: ["Digital Marketing", "Analytics", "Content Strategy", "SEO"],
        status: "Active"
      },
      {
        id: 3,
        firstName: "Emily",
        lastName: "Rodriguez",
        email: "emily.rodriguez@company.com",
        phone: "(555) 345-6789",
        department: "Design",
        position: "UX Designer",
        location: "Austin",
        hireDate: "2022-01-10",
        salary: 72000,
        profileImage: "👩‍🎨",
        skills: ["Figma", "User Research", "Prototyping", "Adobe Creative Suite"],
        status: "Active"
      },
      {
        id: 4,
        firstName: "David",
        lastName: "Thompson",
        email: "david.thompson@company.com",
        phone: "(555) 456-7890",
        department: "Engineering",
        position: "DevOps Engineer",
        location: "Seattle",
        hireDate: "2019-11-05",
        salary: 88000,
        profileImage: "👨‍🔧",
        skills: ["AWS", "Docker", "Kubernetes", "CI/CD"],
        status: "Active"
      },
      {
        id: 5,
        firstName: "Lisa",
        lastName: "Wang",
        email: "lisa.wang@company.com",
        phone: "(555) 567-8901",
        department: "Sales",
        position: "Sales Director",
        location: "Chicago",
        hireDate: "2018-05-14",
        salary: 105000,
        profileImage: "👩‍💼",
        skills: ["Sales Strategy", "CRM", "Team Leadership", "Business Development"],
        status: "Active"
      },
      {
        id: 6,
        firstName: "James",
        lastName: "Wilson",
        email: "james.wilson@company.com",
        phone: "(555) 678-9012",
        department: "HR",
        position: "HR Specialist",
        location: "Denver",
        hireDate: "2021-09-03",
        salary: 58000,
        profileImage: "👨‍💻",
        skills: ["Recruitment", "Employee Relations", "Policy Development", "Training"],
        status: "Active"
      },
      {
        id: 7,
        firstName: "Amanda",
        lastName: "Brown",
        email: "amanda.brown@company.com",
        phone: "(555) 789-0123",
        department: "Engineering",
        position: "Frontend Developer",
        location: "Boston",
        hireDate: "2022-06-20",
        salary: 82000,
        profileImage: "👩‍💻",
        skills: ["React", "TypeScript", "CSS", "JavaScript"],
        status: "Active"
      },
      {
        id: 8,
        firstName: "Robert",
        lastName: "Davis",
        email: "robert.davis@company.com",
        phone: "(555) 890-1234",
        department: "Finance",
        position: "Financial Analyst",
        location: "Miami",
        hireDate: "2020-12-08",
        salary: 68000,
        profileImage: "👨‍💼",
        skills: ["Financial Modeling", "Excel", "SQL", "Data Analysis"],
        status: "Active"
      },
      {
        id: 9,
        firstName: "Jennifer",
        lastName: "Martinez",
        email: "jennifer.martinez@company.com",
        phone: "(555) 901-2345",
        department: "Design",
        position: "Graphic Designer",
        location: "Los Angeles",
        hireDate: "2021-04-12",
        salary: 65000,
        profileImage: "👩‍🎨",
        skills: ["Photoshop", "Illustrator", "Brand Design", "Print Design"],
        status: "On Leave"
      },
      {
        id: 10,
        firstName: "Christopher",
        lastName: "Taylor",
        email: "christopher.taylor@company.com",
        phone: "(555) 012-3456",
        department: "Operations",
        position: "Operations Manager",
        location: "Phoenix",
        hireDate: "2019-08-18",
        salary: 85000,
        profileImage: "👨‍💼",
        skills: ["Process Improvement", "Project Management", "Supply Chain", "Analytics"],
        status: "Active"
      }
    ];
    
    export const departments = [
      "All Departments",
      "Engineering",
      "Marketing", 
      "Design",
      "Sales",
      "HR",
      "Finance",
      "Operations"
    ];
    
    export const locations = [
      "All Locations",
      "New York",
      "San Francisco",
      "Austin",
      "Seattle",
      "Chicago",
      "Denver",
      "Boston",
      "Miami",
      "Los Angeles",
      "Phoenix"
    ];

Step 1.4: Create Basic Employee Card Component

  1. Create a components folder in the src directory
  2. Create EmployeeCard.js in the components folder:
    import React from 'react';
    import './EmployeeCard.css';
    
    function EmployeeCard({ employee }) {
      const formatSalary = (salary) => {
        return new Intl.NumberFormat('en-US', {
          style: 'currency',
          currency: 'USD',
          minimumFractionDigits: 0,
          maximumFractionDigits: 0
        }).format(salary);
      };
    
      const formatHireDate = (dateString) => {
        return new Date(dateString).toLocaleDateString('en-US', {
          year: 'numeric',
          month: 'long',
          day: 'numeric'
        });
      };
    
      const getStatusClass = (status) => {
        return status.toLowerCase().replace(' ', '-');
      };
    
      return (
        <div className="employee-card">
          <div className="employee-header">
            <div className="profile-section">
              <span className="profile-image">{employee.profileImage}</span>
              <div className="name-section">
                <h3 className="employee-name">
                  {employee.firstName} {employee.lastName}
                </h3>
                <p className="employee-position">{employee.position}</p>
              </div>
            </div>
            <div className={`status-badge ${getStatusClass(employee.status)}`}>
              {employee.status}
            </div>
          </div>
    
          <div className="employee-details">
            <div className="detail-row">
              <span className="detail-label">Department:</span>
              <span className="detail-value">{employee.department}</span>
            </div>
            <div className="detail-row">
              <span className="detail-label">Location:</span>
              <span className="detail-value">{employee.location}</span>
            </div>
            <div className="detail-row">
              <span className="detail-label">Email:</span>
              <span className="detail-value">{employee.email}</span>
            </div>
            <div className="detail-row">
              <span className="detail-label">Phone:</span>
              <span className="detail-value">{employee.phone}</span>
            </div>
            <div className="detail-row">
              <span className="detail-label">Hire Date:</span>
              <span className="detail-value">{formatHireDate(employee.hireDate)}</span>
            </div>
            <div className="detail-row">
              <span className="detail-label">Salary:</span>
              <span className="detail-value salary">{formatSalary(employee.salary)}</span>
            </div>
          </div>
    
          <div className="skills-section">
            <span className="skills-label">Skills:</span>
            <div className="skills-list">
              {employee.skills.map((skill, index) => (
                <span key={index} className="skill-tag">
                  {skill}
                </span>
              ))}
            </div>
          </div>
        </div>
      );
    }
    
    export default EmployeeCard;

Step 1.5: Create Employee Card Styles

  1. Create EmployeeCard.css in the components folder:
    .employee-card {
      background: white;
      border-radius: 12px;
      box-shadow: 0 4px 15px rgba(0, 0, 0, 0.08);
      padding: 1.5rem;
      margin-bottom: 1rem;
      transition: transform 0.2s ease, box-shadow 0.2s ease;
    }
    
    .employee-card:hover {
      transform: translateY(-2px);
      box-shadow: 0 8px 25px rgba(0, 0, 0, 0.12);
    }
    
    .employee-header {
      display: flex;
      justify-content: space-between;
      align-items: flex-start;
      margin-bottom: 1.5rem;
    }
    
    .profile-section {
      display: flex;
      align-items: center;
      gap: 1rem;
    }
    
    .profile-image {
      font-size: 3rem;
      display: flex;
      align-items: center;
      justify-content: center;
      width: 60px;
      height: 60px;
      background: #f8f9fa;
      border-radius: 50%;
    }
    
    .name-section {
      display: flex;
      flex-direction: column;
    }
    
    .employee-name {
      font-size: 1.4rem;
      color: #2c3e50;
      margin: 0 0 0.3rem 0;
      font-weight: 600;
    }
    
    .employee-position {
      color: #6c757d;
      margin: 0;
      font-size: 1rem;
      font-weight: 500;
    }
    
    .status-badge {
      padding: 0.4rem 0.8rem;
      border-radius: 20px;
      font-size: 0.8rem;
      font-weight: 600;
      text-transform: uppercase;
    }
    
    .status-badge.active {
      background: #d4edda;
      color: #155724;
      border: 1px solid #c3e6cb;
    }
    
    .status-badge.on-leave {
      background: #fff3cd;
      color: #856404;
      border: 1px solid #ffeaa7;
    }
    
    .status-badge.inactive {
      background: #f8d7da;
      color: #721c24;
      border: 1px solid #f5c6cb;
    }
    
    .employee-details {
      display: grid;
      grid-template-columns: 1fr 1fr;
      gap: 0.8rem;
      margin-bottom: 1.5rem;
    }
    
    .detail-row {
      display: flex;
      flex-direction: column;
      gap: 0.2rem;
    }
    
    .detail-label {
      font-size: 0.85rem;
      color: #6c757d;
      font-weight: 600;
      text-transform: uppercase;
      letter-spacing: 0.5px;
    }
    
    .detail-value {
      color: #2c3e50;
      font-weight: 500;
    }
    
    .detail-value.salary {
      color: #28a745;
      font-weight: 600;
    }
    
    .skills-section {
      border-top: 1px solid #e9ecef;
      padding-top: 1rem;
    }
    
    .skills-label {
      font-size: 0.85rem;
      color: #6c757d;
      font-weight: 600;
      text-transform: uppercase;
      letter-spacing: 0.5px;
      display: block;
      margin-bottom: 0.8rem;
    }
    
    .skills-list {
      display: flex;
      flex-wrap: wrap;
      gap: 0.5rem;
    }
    
    .skill-tag {
      background: #e7f3ff;
      color: #0056b3;
      padding: 0.3rem 0.8rem;
      border-radius: 15px;
      font-size: 0.8rem;
      font-weight: 500;
      border: 1px solid #b3d7ff;
    }
    
    @media (max-width: 768px) {
      .employee-header {
        flex-direction: column;
        gap: 1rem;
        align-items: stretch;
      }
    
      .employee-details {
        grid-template-columns: 1fr;
      }
    
      .profile-section {
        justify-content: center;
        text-align: center;
      }
    
      .name-section {
        align-items: center;
      }
    }

Step 1.6: Create Main App Component with List Rendering

  1. Replace the contents of src/App.js with:
    import React, { useState } from 'react';
    import EmployeeCard from './components/EmployeeCard';
    import { employees } from './data/employeeData';
    import './App.css';
    
    function App() {
      const [employeeList, setEmployeeList] = useState(employees);
    
      return (
        <div className="App">
          <div className="employee-directory">
            <header className="directory-header">
              <h1>Employee Directory</h1>
              <p>Find and connect with your colleagues</p>
              <div className="stats">
                <div className="stat-item">
                  <span className="stat-number">{employeeList.length}</span>
                  <span className="stat-label">Total Employees</span>
                </div>
                <div className="stat-item">
                  <span className="stat-number">
                    {employeeList.filter(emp => emp.status === 'Active').length}
                  </span>
                  <span className="stat-label">Active</span>
                </div>
                <div className="stat-item">
                  <span className="stat-number">
                    {new Set(employeeList.map(emp => emp.department)).size}
                  </span>
                  <span className="stat-label">Departments</span>
                </div>
              </div>
            </header>
    
            <main className="directory-content">
              <div className="employees-grid">
                {employeeList.map(employee => (
                  <EmployeeCard
                    key={employee.id}
                    employee={employee}
                  />
                ))}
              </div>
    
              {employeeList.length === 0 && (
                <div className="no-employees">
                  <h3>No employees found</h3>
                  <p>Try adjusting your search or filter criteria.</p>
                </div>
              )}
            </main>
          </div>
        </div>
      );
    }
    
    export default App;

Step 1.7: Add App Styles

  1. Replace the contents of src/App.css with:
    * {
      margin: 0;
      padding: 0;
      box-sizing: border-box;
    }
    
    body {
      font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
      background-color: #f8f9fa;
      line-height: 1.6;
    }
    
    .App {
      min-height: 100vh;
    }
    
    .employee-directory {
      max-width: 1200px;
      margin: 0 auto;
      padding: 2rem;
    }
    
    .directory-header {
      text-align: center;
      margin-bottom: 3rem;
      background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
      color: white;
      padding: 3rem 2rem;
      border-radius: 20px;
      box-shadow: 0 10px 30px rgba(0, 0, 0, 0.1);
    }
    
    .directory-header h1 {
      font-size: 2.5rem;
      margin-bottom: 0.5rem;
    }
    
    .directory-header p {
      font-size: 1.2rem;
      opacity: 0.9;
      margin-bottom: 2rem;
    }
    
    .stats {
      display: flex;
      justify-content: center;
      gap: 3rem;
    }
    
    .stat-item {
      display: flex;
      flex-direction: column;
      align-items: center;
      gap: 0.5rem;
    }
    
    .stat-number {
      font-size: 2rem;
      font-weight: bold;
      color: #ffeb3b;
    }
    
    .stat-label {
      font-size: 0.9rem;
      opacity: 0.8;
      text-transform: uppercase;
      letter-spacing: 0.5px;
    }
    
    .directory-content {
      background: white;
      border-radius: 20px;
      padding: 2rem;
      box-shadow: 0 5px 20px rgba(0, 0, 0, 0.08);
    }
    
    .employees-grid {
      display: grid;
      grid-template-columns: repeat(auto-fit, minmax(400px, 1fr));
      gap: 1.5rem;
    }
    
    .no-employees {
      text-align: center;
      padding: 4rem 2rem;
      color: #6c757d;
    }
    
    .no-employees h3 {
      font-size: 1.5rem;
      margin-bottom: 1rem;
      color: #495057;
    }
    
    @media (max-width: 768px) {
      .employee-directory {
        padding: 1rem;
      }
    
      .directory-header {
        padding: 2rem 1rem;
      }
    
      .directory-header h1 {
        font-size: 2rem;
      }
    
      .stats {
        flex-direction: column;
        gap: 1rem;
      }
    
      .employees-grid {
        grid-template-columns: 1fr;
      }
    
      .directory-content {
        padding: 1rem;
      }
    }

Step 1.8: Test Basic List Rendering

  1. Save all files and view your employee directory in the browser
  2. Verify that all 10 employees are displayed in a responsive grid
  3. Check that each employee card shows all the required information
  4. Test the responsive design by resizing the browser window
  5. Open browser console and verify there are no key warnings

Exercise 1 Summary: You have successfully created an employee directory with basic list rendering using the map() function. You learned how to structure complex data, create reusable card components, implement proper React keys for list items, and handle responsive grid layouts. The directory displays employee information in an organized, visually appealing format with proper component composition.

Exercise 2: Search and Filter Implementation

Estimated Time: 10 minutes

In this exercise, you will implement search and filtering functionality for the employee directory. You'll learn how to filter arrays based on user input, implement real-time search, and create dropdown filters for departments and locations. This demonstrates dynamic list manipulation and state-driven filtering.

Step 2.1: Create Search and Filter Components

  1. Create SearchBar.js in the components folder:

    import React from 'react';
    import './SearchBar.css';
    
    function SearchBar({ searchTerm, setSearchTerm, placeholder = "Search employees..." }) {
      const handleSearchChange = (e) => {
        setSearchTerm(e.target.value);
      };
    
      const clearSearch = () => {
        setSearchTerm('');
      };
    
      return (
        <div className="search-bar">
          <div className="search-input-container">
            <span className="search-icon">🔍</span>
            <input
              type="text"
              className="search-input"
              placeholder={placeholder}
              value={searchTerm}
              onChange={handleSearchChange}
            />
            {searchTerm && (
              <button className="clear-search" onClick={clearSearch}></button>
            )}
          </div>
          {searchTerm && (
            <p className="search-hint">
              Searching for "{searchTerm}" in names, positions, departments, and skills
            </p>
          )}
        </div>
      );
    }
    
    export default SearchBar;
  2. Create SearchBar.css in the components folder:

    .search-bar {
      margin-bottom: 1.5rem;
    }
    
    .search-input-container {
      position: relative;
      display: flex;
      align-items: center;
    }
    
    .search-icon {
      position: absolute;
      left: 1rem;
      font-size: 1.2rem;
      color: #6c757d;
      z-index: 1;
    }
    
    .search-input {
      width: 100%;
      padding: 1rem 1rem 1rem 3rem;
      border: 2px solid #e9ecef;
      border-radius: 25px;
      font-size: 1rem;
      transition: border-color 0.3s ease, box-shadow 0.3s ease;
      background: white;
    }
    
    .search-input:focus {
      outline: none;
      border-color: #667eea;
      box-shadow: 0 0 0 3px rgba(102, 126, 234, 0.1);
    }
    
    .clear-search {
      position: absolute;
      right: 1rem;
      background: #6c757d;
      color: white;
      border: none;
      border-radius: 50%;
      width: 24px;
      height: 24px;
      display: flex;
      align-items: center;
      justify-content: center;
      cursor: pointer;
      font-size: 0.8rem;
      transition: background-color 0.3s ease;
    }
    
    .clear-search:hover {
      background: #495057;
    }
    
    .search-hint {
      margin-top: 0.5rem;
      font-size: 0.9rem;
      color: #6c757d;
      text-align: center;
      font-style: italic;
    }
  3. Create FilterControls.js in the components folder:

    import React from 'react';
    import './FilterControls.css';
    
    function FilterControls({ 
      departments, 
      locations, 
      selectedDepartment, 
      setSelectedDepartment,
      selectedLocation,
      setSelectedLocation,
      selectedStatus,
      setSelectedStatus,
      onClearFilters
    }) {
      const statusOptions = ['All Status', 'Active', 'On Leave', 'Inactive'];
    
      const hasActiveFilters = 
        selectedDepartment !== 'All Departments' || 
        selectedLocation !== 'All Locations' || 
        selectedStatus !== 'All Status';
    
      return (
        <div className="filter-controls">
          <div className="filter-row">
            <div className="filter-group">
              <label htmlFor="department-filter">Department</label>
              <select
                id="department-filter"
                className="filter-select"
                value={selectedDepartment}
                onChange={(e) => setSelectedDepartment(e.target.value)}
              >
                {departments.map(department => (
                  <option key={department} value={department}>
                    {department}
                  </option>
                ))}
              </select>
            </div>
    
            <div className="filter-group">
              <label htmlFor="location-filter">Location</label>
              <select
                id="location-filter"
                className="filter-select"
                value={selectedLocation}
                onChange={(e) => setSelectedLocation(e.target.value)}
              >
                {locations.map(location => (
                  <option key={location} value={location}>
                    {location}
                  </option>
                ))}
              </select>
            </div>
    
            <div className="filter-group">
              <label htmlFor="status-filter">Status</label>
              <select
                id="status-filter"
                className="filter-select"
                value={selectedStatus}
                onChange={(e) => setSelectedStatus(e.target.value)}
              >
                {statusOptions.map(status => (
                  <option key={status} value={status}>
                    {status}
                  </option>
                ))}
              </select>
            </div>
    
            {hasActiveFilters && (
              <div className="filter-group">
                <label>&nbsp;</label>
                <button 
                  className="clear-filters-btn"
                  onClick={onClearFilters}
                  title="Clear all filters"
                >
                  Clear Filters
                </button>
              </div>
            )}
          </div>
    
          {hasActiveFilters && (
            <div className="active-filters">
              <span className="active-filters-label">Active filters:</span>
              <div className="filter-tags">
                {selectedDepartment !== 'All Departments' && (
                  <span className="filter-tag">
                    Department: {selectedDepartment}
                    <button onClick={() => setSelectedDepartment('All Departments')}></button>
                  </span>
                )}
                {selectedLocation !== 'All Locations' && (
                  <span className="filter-tag">
                    Location: {selectedLocation}
                    <button onClick={() => setSelectedLocation('All Locations')}></button>
                  </span>
                )}
                {selectedStatus !== 'All Status' && (
                  <span className="filter-tag">
                    Status: {selectedStatus}
                    <button onClick={() => setSelectedStatus('All Status')}></button>
                  </span>
                )}
              </div>
            </div>
          )}
        </div>
      );
    }
    
    export default FilterControls;
  4. Create FilterControls.css in the components folder:

    .filter-controls {
      background: #f8f9fa;
      padding: 1.5rem;
      border-radius: 12px;
      margin-bottom: 2rem;
      border: 1px solid #e9ecef;
    }
    
    .filter-row {
      display: grid;
      grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
      gap: 1rem;
      align-items: end;
    }
    
    .filter-group {
      display: flex;
      flex-direction: column;
      gap: 0.5rem;
    }
    
    .filter-group label {
      font-weight: 600;
      color: #495057;
      font-size: 0.9rem;
    }
    
    .filter-select {
      padding: 0.75rem;
      border: 2px solid #e9ecef;
      border-radius: 8px;
      font-size: 1rem;
      background: white;
      cursor: pointer;
      transition: border-color 0.3s ease;
    }
    
    .filter-select:focus {
      outline: none;
      border-color: #667eea;
    }
    
    .clear-filters-btn {
      background: #dc3545;
      color: white;
      border: none;
      padding: 0.75rem 1rem;
      border-radius: 8px;
      cursor: pointer;
      font-weight: 600;
      transition: background-color 0.3s ease;
      white-space: nowrap;
    }
    
    .clear-filters-btn:hover {
      background: #c82333;
    }
    
    .active-filters {
      margin-top: 1rem;
      padding-top: 1rem;
      border-top: 1px solid #dee2e6;
    }
    
    .active-filters-label {
      font-weight: 600;
      color: #495057;
      margin-right: 1rem;
    }
    
    .filter-tags {
      display: inline-flex;
      flex-wrap: wrap;
      gap: 0.5rem;
    }
    
    .filter-tag {
      background: #667eea;
      color: white;
      padding: 0.3rem 0.8rem;
      border-radius: 15px;
      font-size: 0.8rem;
      display: flex;
      align-items: center;
      gap: 0.5rem;
    }
    
    .filter-tag button {
      background: rgba(255, 255, 255, 0.2);
      border: none;
      color: white;
      border-radius: 50%;
      width: 16px;
      height: 16px;
      display: flex;
      align-items: center;
      justify-content: center;
      cursor: pointer;
      font-size: 0.7rem;
    }
    
    .filter-tag button:hover {
      background: rgba(255, 255, 255, 0.3);
    }
    
    @media (max-width: 768px) {
      .filter-row {
        grid-template-columns: 1fr;
      }
    
      .filter-tags {
        margin-top: 0.5rem;
      }
    }

Step 2.2: Update App Component with Search and Filter Logic

  1. Update src/App.js to include search and filtering functionality:
    import React, { useState, useMemo } from 'react';
    import EmployeeCard from './components/EmployeeCard';
    import SearchBar from './components/SearchBar';
    import FilterControls from './components/FilterControls';
    import { employees, departments, locations } from './data/employeeData';
    import './App.css';
    
    function App() {
      // State for search and filters
      const [searchTerm, setSearchTerm] = useState('');
      const [selectedDepartment, setSelectedDepartment] = useState('All Departments');
      const [selectedLocation, setSelectedLocation] = useState('All Locations');
      const [selectedStatus, setSelectedStatus] = useState('All Status');
    
      // Filtered employee list using useMemo for performance
      const filteredEmployees = useMemo(() => {
        let filtered = employees;
    
        // Apply search filter
        if (searchTerm) {
          const searchLower = searchTerm.toLowerCase();
          filtered = filtered.filter(employee => {
            const fullName = `${employee.firstName} ${employee.lastName}`.toLowerCase();
            const position = employee.position.toLowerCase();
            const department = employee.department.toLowerCase();
            const skills = employee.skills.join(' ').toLowerCase();
            const email = employee.email.toLowerCase();
            
            return fullName.includes(searchLower) ||
                   position.includes(searchLower) ||
                   department.includes(searchLower) ||
                   skills.includes(searchLower) ||
                   email.includes(searchLower);
          });
        }
    
        // Apply department filter
        if (selectedDepartment !== 'All Departments') {
          filtered = filtered.filter(employee => 
            employee.department === selectedDepartment
          );
        }
    
        // Apply location filter
        if (selectedLocation !== 'All Locations') {
          filtered = filtered.filter(employee => 
            employee.location === selectedLocation
          );
        }
    
        // Apply status filter
        if (selectedStatus !== 'All Status') {
          filtered = filtered.filter(employee => 
            employee.status === selectedStatus
          );
        }
    
        return filtered;
      }, [employees, searchTerm, selectedDepartment, selectedLocation, selectedStatus]);
    
      // Function to clear all filters
      const clearAllFilters = () => {
        setSearchTerm('');
        setSelectedDepartment('All Departments');
        setSelectedLocation('All Locations');
        setSelectedStatus('All Status');
      };
    
      // Calculate statistics
      const stats = useMemo(() => {
        return {
          total: filteredEmployees.length,
          active: filteredEmployees.filter(emp => emp.status === 'Active').length,
          departments: new Set(filteredEmployees.map(emp => emp.department)).size
        };
      }, [filteredEmployees]);
    
      return (
        <div className="App">
          <div className="employee-directory">
            <header className="directory-header">
              <h1>Employee Directory</h1>
              <p>Find and connect with your colleagues</p>
              <div className="stats">
                <div className="stat-item">
                  <span className="stat-number">{stats.total}</span>
                  <span className="stat-label">
                    {searchTerm || selectedDepartment !== 'All Departments' || 
                     selectedLocation !== 'All Locations' || selectedStatus !== 'All Status' 
                      ? 'Filtered Results' 
                      : 'Total Employees'}
                  </span>
                </div>
                <div className="stat-item">
                  <span className="stat-number">{stats.active}</span>
                  <span className="stat-label">Active</span>
                </div>
                <div className="stat-item">
                  <span className="stat-number">{stats.departments}</span>
                  <span className="stat-label">Departments</span>
                </div>
              </div>
            </header>
    
            <main className="directory-content">
              {/* Search Bar */}
              <SearchBar 
                searchTerm={searchTerm}
                setSearchTerm={setSearchTerm}
                placeholder="Search by name, position, department, skills, or email..."
              />
    
              {/* Filter Controls */}
              <FilterControls
                departments={departments}
                locations={locations}
                selectedDepartment={selectedDepartment}
                setSelectedDepartment={setSelectedDepartment}
                selectedLocation={selectedLocation}
                setSelectedLocation={setSelectedLocation}
                selectedStatus={selectedStatus}
                setSelectedStatus={setSelectedStatus}
                onClearFilters={clearAllFilters}
              />
    
              {/* Results Summary */}
              {(searchTerm || selectedDepartment !== 'All Departments' || 
                selectedLocation !== 'All Locations' || selectedStatus !== 'All Status') && (
                <div className="results-summary">
                  <p>
                    Showing {filteredEmployees.length} of {employees.length} employees
                    {searchTerm && ` matching "${searchTerm}"`}
                  </p>
                </div>
              )}
    
              {/* Employee Grid */}
              <div className="employees-grid">
                {filteredEmployees.map(employee => (
                  <EmployeeCard
                    key={employee.id}
                    employee={employee}
                  />
                ))}
              </div>
    
              {/* No Results Message */}
              {filteredEmployees.length === 0 && (
                <div className="no-employees">
                  <h3>No employees found</h3>
                  <p>
                    {searchTerm 
                      ? `No employees match your search for "${searchTerm}".`
                      : 'No employees match your current filters.'
                    }
                  </p>
                  <button className="clear-all-btn" onClick={clearAllFilters}>
                    Clear All Filters
                  </button>
                </div>
              )}
            </main>
          </div>
        </div>
      );
    }
    
    export default App;

Step 2.3: Update App Styles

  1. Add these styles to src/App.css:
    /* Add these new styles to the existing App.css */
    
    .results-summary {
      background: #e7f3ff;
      padding: 1rem;
      border-radius: 8px;
      margin-bottom: 1.5rem;
      border-left: 4px solid #0056b3;
    }
    
    .results-summary p {
      color: #0056b3;
      font-weight: 600;
      margin: 0;
    }
    
    .clear-all-btn {
      background: #667eea;
      color: white;
      border: none;
      padding: 0.8rem 1.5rem;
      border-radius: 8px;
      cursor: pointer;
      font-weight: 600;
      margin-top: 1rem;
      transition: background-color 0.3s ease;
    }
    
    .clear-all-btn:hover {
      background: #5a67d8;
    }
    
    /* Update existing no-employees styles */
    .no-employees {
      text-align: center;
      padding: 4rem 2rem;
      color: #6c757d;
      background: #f8f9fa;
      border-radius: 12px;
      margin-top: 2rem;
    }
    
    .no-employees h3 {
      font-size: 1.5rem;
      margin-bottom: 1rem;
      color: #495057;
    }
    
    .no-employees p {
      margin-bottom: 1.5rem;
      font-size: 1.1rem;
    }

Step 2.4: Test Search and Filter Functionality

  1. Save all files and test the search and filter features:

    Search Testing:

    • Search by employee names (e.g., "Sarah", "Michael")
    • Search by positions (e.g., "Engineer", "Manager")
    • Search by departments (e.g., "Engineering", "Marketing")
    • Search by skills (e.g., "React", "JavaScript")
    • Search by email domains (e.g., "@company.com")
    • Test partial matches and case insensitivity

    Filter Testing:

    • Filter by different departments
    • Filter by different locations
    • Filter by status (Active, On Leave)
    • Combine multiple filters
    • Use active filter tags to remove individual filters

    Performance Testing:

    • Notice how the statistics update in real-time
    • Observe the filtered results count
    • Test the "Clear All Filters" functionality
  2. Verify the following behaviors:

    • Search is case-insensitive and matches partial text
    • Filters can be combined and work together
    • Active filter tags appear when filters are applied
    • Statistics update to reflect filtered results
    • Clear buttons work for individual filters and all filters
    • No results message appears when no employees match

Exercise 2 Summary: You have successfully implemented comprehensive search and filtering functionality for the employee directory. You learned how to filter arrays based on multiple criteria, implement real-time search across multiple fields, use useMemo for performance optimization, create interactive filter controls, and provide clear user feedback about active filters and results. The filtering system demonstrates advanced array manipulation and state-driven UI updates.

Exercise 3: Sorting and Performance Optimization

Estimated Time: 12 minutes

In this exercise, you will implement sorting functionality for the employee list and add performance optimizations for large datasets. You'll learn how to sort arrays by different criteria, implement dynamic sort controls, and optimize list rendering performance using React keys and memoization techniques.

Step 3.1: Create Sort Controls Component

  1. Create SortControls.js in the components folder:

    import React from 'react';
    import './SortControls.css';
    
    function SortControls({ sortBy, setSortBy, sortOrder, setSortOrder }) {
      const sortOptions = [
        { value: 'name', label: 'Name' },
        { value: 'position', label: 'Position' },
        { value: 'department', label: 'Department' },
        { value: 'location', label: 'Location' },
        { value: 'hireDate', label: 'Hire Date' },
        { value: 'salary', label: 'Salary' }
      ];
    
      const handleSortChange = (newSortBy) => {
        if (sortBy === newSortBy) {
          // If clicking the same sort option, toggle order
          setSortOrder(sortOrder === 'asc' ? 'desc' : 'asc');
        } else {
          // If clicking a different sort option, set new sort and default to ascending
          setSortBy(newSortBy);
          setSortOrder('asc');
        }
      };
    
      const getSortIcon = (optionValue) => {
        if (sortBy !== optionValue) {
          return '↕️'; // Neutral sort icon
        }
        return sortOrder === 'asc' ? '↑' : '↓';
      };
    
      const getSortButtonClass = (optionValue) => {
        return `sort-button ${sortBy === optionValue ? 'active' : ''}`;
      };
    
      return (
        <div className="sort-controls">
          <div className="sort-header">
            <h4>Sort by:</h4>
            <span className="sort-hint">
              Click again to reverse order
            </span>
          </div>
          
          <div className="sort-buttons">
            {sortOptions.map(option => (
              <button
                key={option.value}
                className={getSortButtonClass(option.value)}
                onClick={() => handleSortChange(option.value)}
                title={`Sort by ${option.label} ${
                  sortBy === option.value 
                    ? (sortOrder === 'asc' ? '(click for descending)' : '(click for ascending)')
                    : '(ascending)'
                }`}
              >
                <span className="sort-label">{option.label}</span>
                <span className="sort-icon">{getSortIcon(option.value)}</span>
              </button>
            ))}
          </div>
    
          <div className="current-sort">
            Currently sorted by: <strong>{sortOptions.find(opt => opt.value === sortBy)?.label}</strong>
            {' '}({sortOrder === 'asc' ? 'A-Z' : 'Z-A'})
          </div>
        </div>
      );
    }
    
    export default SortControls;
  2. Create SortControls.css in the components folder:

    .sort-controls {
      background: white;
      padding: 1.5rem;
      border-radius: 12px;
      margin-bottom: 1.5rem;
      border: 1px solid #e9ecef;
      box-shadow: 0 2px 8px rgba(0, 0, 0, 0.05);
    }
    
    .sort-header {
      display: flex;
      justify-content: space-between;
      align-items: center;
      margin-bottom: 1rem;
    }
    
    .sort-header h4 {
      color: #2c3e50;
      margin: 0;
      font-size: 1.1rem;
    }
    
    .sort-hint {
      font-size: 0.85rem;
      color: #6c757d;
      font-style: italic;
    }
    
    .sort-buttons {
      display: flex;
      flex-wrap: wrap;
      gap: 0.5rem;
      margin-bottom: 1rem;
    }
    
    .sort-button {
      display: flex;
      align-items: center;
      gap: 0.5rem;
      padding: 0.6rem 1rem;
      border: 2px solid #e9ecef;
      background: white;
      border-radius: 8px;
      cursor: pointer;
      transition: all 0.3s ease;
      font-size: 0.9rem;
      font-weight: 500;
    }
    
    .sort-button:hover {
      border-color: #667eea;
      background: #f8f9ff;
      transform: translateY(-1px);
    }
    
    .sort-button.active {
      background: #667eea;
      border-color: #667eea;
      color: white;
    }
    
    .sort-button.active:hover {
      background: #5a67d8;
      border-color: #5a67d8;
    }
    
    .sort-label {
      font-weight: 600;
    }
    
    .sort-icon {
      font-size: 0.9rem;
      font-weight: bold;
    }
    
    .current-sort {
      font-size: 0.9rem;
      color: #495057;
      padding: 0.8rem;
      background: #f8f9fa;
      border-radius: 6px;
      text-align: center;
    }
    
    .current-sort strong {
      color: #667eea;
    }
    
    @media (max-width: 768px) {
      .sort-header {
        flex-direction: column;
        gap: 0.5rem;
        align-items: flex-start;
      }
    
      .sort-buttons {
        justify-content: center;
      }
    
      .sort-button {
        flex: 1;
        min-width: 120px;
        justify-content: center;
      }
    }

Step 3.2: Create Performance Monitoring Component

  1. Create PerformanceMonitor.js in the components folder:

    import React, { useState, useEffect } from 'react';
    import './PerformanceMonitor.css';
    
    function PerformanceMonitor({ employeeCount, renderTime }) {
      const [isVisible, setIsVisible] = useState(false);
      const [renderCount, setRenderCount] = useState(0);
    
      useEffect(() => {
        setRenderCount(prev => prev + 1);
      });
    
      const toggleVisibility = () => {
        setIsVisible(!isVisible);
      };
    
      const getPerformanceStatus = () => {
        if (renderTime < 10) return 'excellent';
        if (renderTime < 50) return 'good';
        if (renderTime < 100) return 'fair';
        return 'poor';
      };
    
      return (
      <div className="performance-monitor">
        <button 
          className="performance-toggle"
          onClick={toggleVisibility}
          title="Toggle performance metrics"
        >
          ⚡ Performance
        </button>
    
        {isVisible && (
          <div className="performance-details">
            <div className="performance-header">
              <h5>Rendering Performance</h5>
              <button 
                className="close-btn"
                onClick={() => setIsVisible(false)}
              ></button>
            </div>
    
            <div className="performance-metrics">
              <div className="metric">
                <span className="metric-label">Items Rendered:</span>
                <span className="metric-value">{employeeCount}</span>
              </div>
    
              <div className="metric">
                <span className="metric-label">Render Time:</span>
                <span className={`metric-value ${getPerformanceStatus()}`}>
                  {renderTime.toFixed(2)}ms
                </span>
              </div>
    
              <div className="metric">
                <span className="metric-label">Total Renders:</span>
                <span className="metric-value">{renderCount}</span>
              </div>
    
              <div className="metric">
                <span className="metric-label">Status:</span>
                <span className={`metric-value status-${getPerformanceStatus()}`}>
                  {getPerformanceStatus().toUpperCase()}
                </span>
              </div>
            </div>
    
            <div className="performance-tips">
              <p><strong>Performance Tips:</strong></p>
              <ul>
                <li>React keys help optimize re-renders</li>
                <li>useMemo prevents unnecessary calculations</li>
                <li>Proper component structure improves performance</li>
              </ul>
            </div>
          </div>
        )}
      </div>
      );
    }
    
    export default PerformanceMonitor;
  2. Create PerformanceMonitor.css in the components folder:

    .performance-monitor {
      position: fixed;
      top: 20px;
      right: 20px;
      z-index: 1000;
    }
    
    .performance-toggle {
      background: #28a745;
      color: white;
      border: none;
      padding: 0.5rem 1rem;
      border-radius: 20px;
      cursor: pointer;
      font-size: 0.9rem;
      font-weight: 600;
      box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15);
      transition: all 0.3s ease;
    }
    
    .performance-toggle:hover {
      background: #218838;
      transform: translateY(-1px);
    }
    
    .performance-details {
      position: absolute;
      top: 100%;
      right: 0;
      margin-top: 0.5rem;
      background: white;
      border: 1px solid #e9ecef;
      border-radius: 12px;
      padding: 1rem;
      min-width: 280px;
      box-shadow: 0 5px 20px rgba(0, 0, 0, 0.15);
      animation: slideDown 0.3s ease;
    }
    
    @keyframes slideDown {
      from {
        opacity: 0;
        transform: translateY(-10px);
      }
      to {
        opacity: 1;
        transform: translateY(0);
      }
    }
    
    .performance-header {
      display: flex;
      justify-content: space-between;
      align-items: center;
      margin-bottom: 1rem;
      padding-bottom: 0.5rem;
      border-bottom: 1px solid #e9ecef;
    }
    
    .performance-header h5 {
      margin: 0;
      color: #2c3e50;
    }
    
    .close-btn {
      background: none;
      border: none;
      font-size: 1rem;
      cursor: pointer;
      color: #6c757d;
      padding: 0.2rem;
    }
    
    .close-btn:hover {
      color: #495057;
    }
    
    .performance-metrics {
      display: flex;
      flex-direction: column;
      gap: 0.5rem;
      margin-bottom: 1rem;
    }
    
    .metric {
      display: flex;
      justify-content: space-between;
      align-items: center;
    }
    
    .metric-label {
      font-size: 0.9rem;
      color: #6c757d;
    }
    
    .metric-value {
      font-weight: 600;
      font-size: 0.9rem;
    }
    
    .metric-value.excellent {
      color: #28a745;
    }
    
    .metric-value.good {
      color: #ffc107;
    }
    
    .metric-value.fair {
      color: #fd7e14;
    }
    
    .metric-value.poor {
      color: #dc3545;
    }
    
    .status-excellent {
      color: #28a745;
    }
    
    .status-good {
      color: #ffc107;
    }
    
    .status-fair {
      color: #fd7e14;
    }
    
    .status-poor {
      color: #dc3545;
    }
    
    .performance-tips {
      font-size: 0.8rem;
      color: #6c757d;
      background: #f8f9fa;
      padding: 0.8rem;
      border-radius: 6px;
    }
    
    .performance-tips p {
      margin: 0 0 0.5rem 0;
      font-weight: 600;
    }
    
    .performance-tips ul {
      margin: 0;
      padding-left: 1rem;
    }
    
    .performance-tips li {
      margin-bottom: 0.2rem;
    }

Step 3.3: Update App Component with Sorting and Performance Monitoring

  1. Update src/App.js to include sorting functionality:
    import React, { useState, useMemo } from 'react';
    import EmployeeCard from './components/EmployeeCard';
    import SearchBar from './components/SearchBar';
    import FilterControls from './components/FilterControls';
    import SortControls from './components/SortControls';
    import PerformanceMonitor from './components/PerformanceMonitor';
    import { employees, departments, locations } from './data/employeeData';
    import './App.css';
    
    function App() {
      // Existing state
      const [searchTerm, setSearchTerm] = useState('');
      const [selectedDepartment, setSelectedDepartment] = useState('All Departments');
      const [selectedLocation, setSelectedLocation] = useState('All Locations');
      const [selectedStatus, setSelectedStatus] = useState('All Status');
    
      // New sorting state
      const [sortBy, setSortBy] = useState('name');
      const [sortOrder, setSortOrder] = useState('asc');
    
      // Performance monitoring
      const [renderTime, setRenderTime] = useState(0);
    
      // Enhanced filtered and sorted employee list
      const processedEmployees = useMemo(() => {
        const startTime = performance.now();
        
        let filtered = employees;
    
        // Apply search filter
        if (searchTerm) {
          const searchLower = searchTerm.toLowerCase();
          filtered = filtered.filter(employee => {
            const fullName = `${employee.firstName} ${employee.lastName}`.toLowerCase();
            const position = employee.position.toLowerCase();
            const department = employee.department.toLowerCase();
            const skills = employee.skills.join(' ').toLowerCase();
            const email = employee.email.toLowerCase();
            
            return fullName.includes(searchLower) ||
                   position.includes(searchLower) ||
                   department.includes(searchLower) ||
                   skills.includes(searchLower) ||
                   email.includes(searchLower);
          });
        }
    
        // Apply department filter
        if (selectedDepartment !== 'All Departments') {
          filtered = filtered.filter(employee => 
            employee.department === selectedDepartment
          );
        }
    
        // Apply location filter
        if (selectedLocation !== 'All Locations') {
          filtered = filtered.filter(employee => 
            employee.location === selectedLocation
          );
        }
    
        // Apply status filter
        if (selectedStatus !== 'All Status') {
          filtered = filtered.filter(employee => 
            employee.status === selectedStatus
          );
        }
    
        // Apply sorting
        const sorted = [...filtered].sort((a, b) => {
          let aValue, bValue;
    
          switch (sortBy) {
            case 'name':
              aValue = `${a.firstName} ${a.lastName}`.toLowerCase();
              bValue = `${b.firstName} ${b.lastName}`.toLowerCase();
              break;
            case 'position':
              aValue = a.position.toLowerCase();
              bValue = b.position.toLowerCase();
              break;
            case 'department':
              aValue = a.department.toLowerCase();
              bValue = b.department.toLowerCase();
              break;
            case 'location':
              aValue = a.location.toLowerCase();
              bValue = b.location.toLowerCase();
              break;
            case 'hireDate':
              aValue = new Date(a.hireDate);
              bValue = new Date(b.hireDate);
              break;
            case 'salary':
              aValue = a.salary;
              bValue = b.salary;
              break;
            default:
              aValue = a.id;
              bValue = b.id;
          }
    
          if (aValue < bValue) {
            return sortOrder === 'asc' ? -1 : 1;
          }
          if (aValue > bValue) {
            return sortOrder === 'asc' ? 1 : -1;
          }
          return 0;
        });
    
        const endTime = performance.now();
        setRenderTime(endTime - startTime);
    
        return sorted;
      }, [employees, searchTerm, selectedDepartment, selectedLocation, selectedStatus, sortBy, sortOrder]);
    
      // Function to clear all filters and sorting
      const clearAllFilters = () => {
        setSearchTerm('');
        setSelectedDepartment('All Departments');
        setSelectedLocation('All Locations');
        setSelectedStatus('All Status');
        setSortBy('name');
        setSortOrder('asc');
      };
    
      // Calculate statistics
      const stats = useMemo(() => {
        return {
          total: processedEmployees.length,
          active: processedEmployees.filter(emp => emp.status === 'Active').length,
          departments: new Set(processedEmployees.map(emp => emp.department)).size
        };
      }, [processedEmployees]);
    
      return (
        <div className="App">
          {/* Performance Monitor */}
          <PerformanceMonitor 
            employeeCount={processedEmployees.length}
            renderTime={renderTime}
          />
    
          <div className="employee-directory">
            <header className="directory-header">
              <h1>Employee Directory</h1>
              <p>Find and connect with your colleagues</p>
              <div className="stats">
                <div className="stat-item">
                  <span className="stat-number">{stats.total}</span>
                  <span className="stat-label">
                    {searchTerm || selectedDepartment !== 'All Departments' || 
                     selectedLocation !== 'All Locations' || selectedStatus !== 'All Status' 
                      ? 'Filtered Results' 
                      : 'Total Employees'}
                  </span>
                </div>
                <div className="stat-item">
                  <span className="stat-number">{stats.active}</span>
                  <span className="stat-label">Active</span>
                </div>
                <div className="stat-item">
                  <span className="stat-number">{stats.departments}</span>
                  <span className="stat-label">Departments</span>
                </div>
              </div>
            </header>
    
            <main className="directory-content">
              {/* Search Bar */}
              <SearchBar 
                searchTerm={searchTerm}
                setSearchTerm={setSearchTerm}
                placeholder="Search by name, position, department, skills, or email..."
              />
    
              {/* Filter Controls */}
              <FilterControls
                departments={departments}
                locations={locations}
                selectedDepartment={selectedDepartment}
                setSelectedDepartment={setSelectedDepartment}
                selectedLocation={selectedLocation}
                setSelectedLocation={setSelectedLocation}
                selectedStatus={selectedStatus}
                setSelectedStatus={setSelectedStatus}
                onClearFilters={clearAllFilters}
              />
    
              {/* Sort Controls */}
              <SortControls
                sortBy={sortBy}
                setSortBy={setSortBy}
                sortOrder={sortOrder}
                setSortOrder={setSortOrder}
              />
    
              {/* Results Summary */}
              {(searchTerm || selectedDepartment !== 'All Departments' || 
                selectedLocation !== 'All Locations' || selectedStatus !== 'All Status') && (
                <div className="results-summary">
                  <p>
                    Showing {processedEmployees.length} of {employees.length} employees
                    {searchTerm && ` matching "${searchTerm}"`}
                    {' '}sorted by {sortBy} ({sortOrder === 'asc' ? 'ascending' : 'descending'})
                  </p>
                </div>
              )}
    
              {/* Employee Grid */}
              <div className="employees-grid">
                {processedEmployees.map(employee => (
                  <EmployeeCard
                    key={employee.id}
                    employee={employee}
                  />
                ))}
              </div>
    
              {/* No Results Message */}
              {processedEmployees.length === 0 && (
                <div className="no-employees">
                  <h3>No employees found</h3>
                  <p>
                    {searchTerm 
                      ? `No employees match your search for "${searchTerm}".`
                      : 'No employees match your current filters.'
                    }
                  </p>
                  <button className="clear-all-btn" onClick={clearAllFilters}>
                    Clear All Filters & Reset Sort
                  </button>
                </div>
              )}
            </main>
          </div>
        </div>
      );
    }
    
    export default App;

Step 3.4: Test Sorting and Performance Features

  1. Save all files and test the sorting functionality:

    Sorting Tests:

    • Click each sort button to sort by different criteria
    • Click the same sort button twice to toggle between ascending/descending
    • Observe the sort icons changing to show current sort direction
    • Notice the "Currently sorted by" indicator updates
    • Test sorting combined with filters and search

    Performance Tests:

    • Click the "⚡ Performance" button in the top-right corner
    • Observe render times as you apply different filters and sorts
    • Notice the render count increasing with each interaction
    • Try rapid filter changes to see performance impact
  2. Verify the following behaviors:

    • Name sorting works alphabetically (first name + last name)
    • Date sorting works chronologically (hire dates)
    • Salary sorting works numerically
    • Sort order toggles correctly when clicking the same option
    • Performance monitor shows realistic metrics
    • Render times should be under 50ms for good performance
  3. Test edge cases:

    • Sort with no results (empty filtered list)
    • Sort with only one employee showing
    • Apply multiple filters then sort
    • Clear all filters and verify sort is reset

Exercise 3 Summary: You have successfully implemented comprehensive sorting functionality and performance monitoring for the employee directory. You learned how to sort arrays by multiple criteria (strings, numbers, dates), create interactive sort controls with visual feedback, implement performance monitoring to track render times, use useMemo for optimization, and provide clear user feedback about current sort state. The sorting system works seamlessly with existing filters and search, demonstrating advanced list manipulation and performance optimization techniques.

Exercise 4: Advanced List Features and Optimization

Estimated Time: 10 minutes

In this exercise, you will implement advanced list features including pagination, bulk actions, and virtualization concepts. You'll learn how to handle large datasets efficiently, implement user-friendly navigation controls, and optimize performance for real-world applications with hundreds or thousands of items.

Step 4.1: Create Pagination Component

  1. Create Pagination.js in the components folder:

    import React from 'react';
    import './Pagination.css';
    
    function Pagination({ 
      currentPage, 
      totalItems, 
      itemsPerPage, 
      onPageChange, 
      onItemsPerPageChange 
    }) {
      const totalPages = Math.ceil(totalItems / itemsPerPage);
      const startItem = (currentPage - 1) * itemsPerPage + 1;
      const endItem = Math.min(currentPage * itemsPerPage, totalItems);
    
      const getPageNumbers = () => {
        const pages = [];
        const maxVisiblePages = 5;
        
        if (totalPages <= maxVisiblePages) {
          // Show all pages if total pages is small
          for (let i = 1; i <= totalPages; i++) {
            pages.push(i);
          }
        } else {
          // Show smart pagination with ellipsis
          if (currentPage <= 3) {
            // Show first 4 pages + ellipsis + last page
            pages.push(1, 2, 3, 4, '...', totalPages);
          } else if (currentPage >= totalPages - 2) {
            // Show first page + ellipsis + last 4 pages
            pages.push(1, '...', totalPages - 3, totalPages - 2, totalPages - 1, totalPages);
          } else {
            // Show first page + ellipsis + current-1, current, current+1 + ellipsis + last page
            pages.push(1, '...', currentPage - 1, currentPage, currentPage + 1, '...', totalPages);
          }
        }
        
        return pages;
      };
    
      const itemsPerPageOptions = [5, 10, 20, 50];
    
      if (totalItems === 0) {
        return null;
      }
    
      return (
        <div className="pagination">
          <div className="pagination-info">
            <span className="items-info">
              Showing {startItem}-{endItem} of {totalItems} employees
            </span>
            
            <div className="items-per-page">
              <label htmlFor="items-per-page">Show:</label>
              <select
                id="items-per-page"
                value={itemsPerPage}
                onChange={(e) => onItemsPerPageChange(Number(e.target.value))}
                className="items-select"
              >
                {itemsPerPageOptions.map(option => (
                  <option key={option} value={option}>
                    {option} per page
                  </option>
                ))}
              </select>
            </div>
          </div>
    
          {totalPages > 1 && (
            <div className="pagination-controls">
              <button
                className="page-btn"
                onClick={() => onPageChange(currentPage - 1)}
                disabled={currentPage === 1}
                title="Previous page"
              >
                ← Previous
              </button>
    
              <div className="page-numbers">
                {getPageNumbers().map((page, index) => (
                  <span key={index}>
                    {page === '...' ? (
                      <span className="ellipsis">...</span>
                    ) : (
                      <button
                        className={`page-number ${currentPage === page ? 'active' : ''}`}
                        onClick={() => onPageChange(page)}
                      >
                        {page}
                      </button>
                    )}
                  </span>
                ))}
              </div>
    
              <button
                className="page-btn"
                onClick={() => onPageChange(currentPage + 1)}
                disabled={currentPage === totalPages}
                title="Next page"
              >
                Next →
              </button>
            </div>
          )}
        </div>
      );
    }
    
    export default Pagination;
  2. Create Pagination.css in the components folder:

    .pagination {
      background: white;
      padding: 1.5rem;
      border-radius: 12px;
      border: 1px solid #e9ecef;
      margin-top: 2rem;
    }
    
    .pagination-info {
      display: flex;
      justify-content: space-between;
      align-items: center;
      margin-bottom: 1rem;
      padding-bottom: 1rem;
      border-bottom: 1px solid #e9ecef;
    }
    
    .items-info {
      color: #6c757d;
      font-weight: 500;
    }
    
    .items-per-page {
      display: flex;
      align-items: center;
      gap: 0.5rem;
    }
    
    .items-per-page label {
      font-weight: 600;
      color: #495057;
    }
    
    .items-select {
      padding: 0.4rem 0.8rem;
      border: 1px solid #e9ecef;
      border-radius: 6px;
      font-size: 0.9rem;
      cursor: pointer;
    }
    
    .pagination-controls {
      display: flex;
      justify-content: center;
      align-items: center;
      gap: 0.5rem;
    }
    
    .page-btn {
      padding: 0.6rem 1rem;
      border: 1px solid #e9ecef;
      background: white;
      border-radius: 6px;
      cursor: pointer;
      font-weight: 500;
      transition: all 0.3s ease;
    }
    
    .page-btn:hover:not(:disabled) {
      background: #f8f9fa;
      border-color: #667eea;
    }
    
    .page-btn:disabled {
      opacity: 0.5;
      cursor: not-allowed;
    }
    
    .page-numbers {
      display: flex;
      gap: 0.2rem;
    }
    
    .page-number {
      width: 40px;
      height: 40px;
      border: 1px solid #e9ecef;
      background: white;
      border-radius: 6px;
      cursor: pointer;
      font-weight: 500;
      transition: all 0.3s ease;
    }
    
    .page-number:hover {
      background: #f8f9fa;
      border-color: #667eea;
    }
    
    .page-number.active {
      background: #667eea;
      border-color: #667eea;
      color: white;
    }
    
    .ellipsis {
      display: flex;
      align-items: center;
      justify-content: center;
      width: 40px;
      height: 40px;
      color: #6c757d;
      font-weight: bold;
    }
    
    @media (max-width: 768px) {
      .pagination-info {
        flex-direction: column;
        gap: 1rem;
        align-items: stretch;
        text-align: center;
      }
    
      .pagination-controls {
        flex-wrap: wrap;
        gap: 0.3rem;
      }
    
      .page-btn {
        font-size: 0.9rem;
        padding: 0.5rem 0.8rem;
      }
    
      .page-number {
        width: 35px;
        height: 35px;
        font-size: 0.9rem;
      }
    }

Step 4.2: Create Bulk Actions Component

  1. Create BulkActions.js in the components folder:

    import React, { useState } from 'react';
    import './BulkActions.css';
    
    function BulkActions({ 
      selectedEmployees, 
      onSelectAll, 
      onDeselectAll, 
      totalEmployees,
      onBulkExport,
      onBulkStatusChange 
    }) {
      const [showActions, setShowActions] = useState(false);
    
      const selectedCount = selectedEmployees.length;
      const isAllSelected = selectedCount === totalEmployees && totalEmployees > 0;
      const isPartialSelected = selectedCount > 0 && selectedCount < totalEmployees;
    
      const handleSelectAll = () => {
        if (isAllSelected) {
          onDeselectAll();
        } else {
          onSelectAll();
        }
      };
    
      const handleBulkAction = (action) => {
        switch (action) {
          case 'export':
            onBulkExport(selectedEmployees);
            break;
          case 'activate':
            onBulkStatusChange(selectedEmployees, 'Active');
            break;
          case 'deactivate':
            onBulkStatusChange(selectedEmployees, 'Inactive');
            break;
          default:
            break;
        }
        setShowActions(false);
      };
    
      if (totalEmployees === 0) {
        return null;
      }
    
      return (
        <div className="bulk-actions">
          <div className="selection-controls">
            <label className="select-all-container">
              <input
                type="checkbox"
                checked={isAllSelected}
                ref={input => {
                  if (input) input.indeterminate = isPartialSelected;
                }}
                onChange={handleSelectAll}
                className="select-all-checkbox"
              />
              <span className="checkmark"></span>
              <span className="select-text">
                {isAllSelected 
                  ? `All ${totalEmployees} selected` 
                  : selectedCount > 0 
                    ? `${selectedCount} selected`
                    : 'Select all'
                }
              </span>
            </label>
    
            {selectedCount > 0 && (
              <button 
                className="clear-selection"
                onClick={onDeselectAll}
                title="Clear selection"
              >
                Clear
              </button>
            )}
          </div>
    
          {selectedCount > 0 && (
            <div className="bulk-action-controls">
              <button 
                className="bulk-actions-toggle"
                onClick={() => setShowActions(!showActions)}
              >
                Actions ({selectedCount}) {showActions ? '▼' : '▶'}
              </button>
    
              {showActions && (
                <div className="bulk-actions-menu">
                  <button 
                    className="bulk-action-btn export"
                    onClick={() => handleBulkAction('export')}
                  >
                    📄 Export Selected
                  </button>
                  
                  <button 
                    className="bulk-action-btn activate"
                    onClick={() => handleBulkAction('activate')}
                  >
                    ✅ Mark as Active
                  </button>
                  
                  <button 
                    className="bulk-action-btn deactivate"
                    onClick={() => handleBulkAction('deactivate')}
                  >
                    ❌ Mark as Inactive
                  </button>
                </div>
              )}
            </div>
          )}
        </div>
      );
    }
    
    export default BulkActions;
  2. Create BulkActions.css in the components folder:

    .bulk-actions {
      background: #f8f9fa;
      padding: 1rem 1.5rem;
      border-radius: 8px;
      border: 1px solid #e9ecef;
      margin-bottom: 1rem;
      display: flex;
      justify-content: space-between;
      align-items: center;
      flex-wrap: wrap;
      gap: 1rem;
    }
    
    .selection-controls {
      display: flex;
      align-items: center;
      gap: 1rem;
    }
    
    .select-all-container {
      display: flex;
      align-items: center;
      gap: 0.5rem;
      cursor: pointer;
      font-weight: 500;
    }
    
    .select-all-checkbox {
      display: none;
    }
    
    .checkmark {
      width: 20px;
      height: 20px;
      border: 2px solid #6c757d;
      border-radius: 4px;
      position: relative;
      transition: all 0.3s ease;
    }
    
    .select-all-container input:checked + .checkmark {
      background: #667eea;
      border-color: #667eea;
    }
    
    .select-all-container input:indeterminate + .checkmark {
      background: #ffc107;
      border-color: #ffc107;
    }
    
    .select-all-container input:checked + .checkmark::after {
      content: '✓';
      position: absolute;
      top: -2px;
      left: 3px;
      color: white;
      font-weight: bold;
      font-size: 14px;
    }
    
    .select-all-container input:indeterminate + .checkmark::after {
      content: '−';
      position: absolute;
      top: -2px;
      left: 4px;
      color: white;
      font-weight: bold;
      font-size: 14px;
    }
    
    .select-text {
      color: #495057;
    }
    
    .clear-selection {
      background: #dc3545;
      color: white;
      border: none;
      padding: 0.4rem 0.8rem;
      border-radius: 4px;
      cursor: pointer;
      font-size: 0.9rem;
      transition: background-color 0.3s ease;
    }
    
    .clear-selection:hover {
      background: #c82333;
    }
    
    .bulk-action-controls {
      position: relative;
    }
    
    .bulk-actions-toggle {
      background: #667eea;
      color: white;
      border: none;
      padding: 0.6rem 1rem;
      border-radius: 6px;
      cursor: pointer;
      font-weight: 500;
      transition: background-color 0.3s ease;
    }
    
    .bulk-actions-toggle:hover {
      background: #5a67d8;
    }
    
    .bulk-actions-menu {
      position: absolute;
      top: 100%;
      right: 0;
      margin-top: 0.5rem;
      background: white;
      border: 1px solid #e9ecef;
      border-radius: 8px;
      box-shadow: 0 5px 15px rgba(0, 0, 0, 0.15);
      z-index: 100;
      min-width: 180px;
    }
    
    .bulk-action-btn {
      width: 100%;
      padding: 0.8rem 1rem;
      border: none;
      background: white;
      text-align: left;
      cursor: pointer;
      transition: background-color 0.3s ease;
      font-size: 0.9rem;
      display: flex;
      align-items: center;
      gap: 0.5rem;
    }
    
    .bulk-action-btn:hover {
      background: #f8f9fa;
    }
    
    .bulk-action-btn:first-child {
      border-radius: 8px 8px 0 0;
    }
    
    .bulk-action-btn:last-child {
      border-radius: 0 0 8px 8px;
    }
    
    .bulk-action-btn.export {
      color: #007bff;
    }
    
    .bulk-action-btn.activate {
      color: #28a745;
    }
    
    .bulk-action-btn.deactivate {
      color: #dc3545;
    }
    
    @media (max-width: 768px) {
      .bulk-actions {
        flex-direction: column;
        align-items: stretch;
      }
    
      .selection-controls {
        justify-content: center;
      }
    
      .bulk-actions-menu {
        position: static;
        margin-top: 1rem;
        box-shadow: none;
        border: 1px solid #e9ecef;
      }
    }

Step 4.3: Update EmployeeCard for Selection

  1. Update EmployeeCard.js to include selection functionality:

    import React from 'react';
    import './EmployeeCard.css';
    
    function EmployeeCard({ employee, isSelected, onSelectionChange }) {
      const formatSalary = (salary) => {
        return new Intl.NumberFormat('en-US', {
          style: 'currency',
          currency: 'USD',
          minimumFractionDigits: 0,
          maximumFractionDigits: 0
        }).format(salary);
      };
    
      const formatHireDate = (dateString) => {
        return new Date(dateString).toLocaleDateString('en-US', {
          year: 'numeric',
          month: 'long',
          day: 'numeric'
        });
      };
    
      const getStatusClass = (status) => {
        return status.toLowerCase().replace(' ', '-');
      };
    
      const handleCardClick = (e) => {
        // Don't trigger selection if clicking on interactive elements
        if (e.target.closest('.employee-checkbox')) {
          return;
        }
        onSelectionChange && onSelectionChange(employee.id);
      };
    
      return (
        <div 
          className={`employee-card ${isSelected ? 'selected' : ''}`}
          onClick={handleCardClick}
        >
          {/* Selection checkbox */}
          <div className="employee-checkbox" onClick={(e) => e.stopPropagation()}>
            <input
              type="checkbox"
              checked={isSelected || false}
              onChange={() => onSelectionChange && onSelectionChange(employee.id)}
              id={`select-${employee.id}`}
            />
            <label htmlFor={`select-${employee.id}`} className="checkbox-label"></label>
          </div>
    
          <div className="employee-header">
            <div className="profile-section">
              <span className="profile-image">{employee.profileImage}</span>
              <div className="name-section">
                <h3 className="employee-name">
                  {employee.firstName} {employee.lastName}
                </h3>
                <p className="employee-position">{employee.position}</p>
              </div>
            </div>
            <div className={`status-badge ${getStatusClass(employee.status)}`}>
              {employee.status}
            </div>
          </div>
    
          <div className="employee-details">
            <div className="detail-row">
              <span className="detail-label">Department:</span>
              <span className="detail-value">{employee.department}</span>
            </div>
            <div className="detail-row">
              <span className="detail-label">Location:</span>
              <span className="detail-value">{employee.location}</span>
            </div>
            <div className="detail-row">
              <span className="detail-label">Email:</span>
              <span className="detail-value">{employee.email}</span>
            </div>
            <div className="detail-row">
              <span className="detail-label">Phone:</span>
              <span className="detail-value">{employee.phone}</span>
            </div>
            <div className="detail-row">
              <span className="detail-label">Hire Date:</span>
              <span className="detail-value">{formatHireDate(employee.hireDate)}</span>
            </div>
            <div className="detail-row">
              <span className="detail-label">Salary:</span>
              <span className="detail-value salary">{formatSalary(employee.salary)}</span>
            </div>
          </div>
    
          <div className="skills-section">
            <span className="skills-label">Skills:</span>
            <div className="skills-list">
              {employee.skills.map((skill, index) => (
                <span key={index} className="skill-tag">
                  {skill}
                </span>
              ))}
            </div>
          </div>
        </div>
      );
    }
    
    export default EmployeeCard;
  2. Update EmployeeCard.css to include selection styles:

    /* Add these styles to the existing EmployeeCard.css */
    
    .employee-card {
      position: relative;
      cursor: pointer;
      user-select: none;
    }
    
    .employee-card.selected {
      border: 2px solid #667eea;
      background: #f8f9ff;
    }
    
    .employee-checkbox {
      position: absolute;
      top: 1rem;
      right: 1rem;
      z-index: 10;
    }
    
    .employee-checkbox input[type="checkbox"] {
      display: none;
    }
    
    .checkbox-label {
      display: block;
      width: 20px;
      height: 20px;
      border: 2px solid #6c757d;
      border-radius: 4px;
      cursor: pointer;
      position: relative;
      transition: all 0.3s ease;
      background: white;
    }
    
    .employee-checkbox input[type="checkbox"]:checked + .checkbox-label {
      background: #667eea;
      border-color: #667eea;
    }
    
    .employee-checkbox input[type="checkbox"]:checked + .checkbox-label::after {
      content: '✓';
      position: absolute;
      top: -2px;
      left: 3px;
      color: white;
      font-weight: bold;
      font-size: 14px;
    }
    
    .employee-card:hover .checkbox-label {
      border-color: #667eea;
    }
    
    /* Ensure header doesn't overlap with checkbox */
    .employee-header {
      margin-right: 2.5rem;
    }

Step 4.4: Final App Component with Pagination and Bulk Actions

  1. Update src/App.js with final advanced features:
    import React, { useState, useMemo } from 'react';
    import EmployeeCard from './components/EmployeeCard';
    import SearchBar from './components/SearchBar';
    import FilterControls from './components/FilterControls';
    import SortControls from './components/SortControls';
    import BulkActions from './components/BulkActions';
    import Pagination from './components/Pagination';
    import PerformanceMonitor from './components/PerformanceMonitor';
    import { employees, departments, locations } from './data/employeeData';
    import './App.css';
    
    function App() {
      // Existing state
      const [searchTerm, setSearchTerm] = useState('');
      const [selectedDepartment, setSelectedDepartment] = useState('All Departments');
      const [selectedLocation, setSelectedLocation] = useState('All Locations');
      const [selectedStatus, setSelectedStatus] = useState('All Status');
      const [sortBy, setSortBy] = useState('name');
      const [sortOrder, setSortOrder] = useState('asc');
      
      // New state for advanced features
      const [selectedEmployees, setSelectedEmployees] = useState([]);
      const [currentPage, setCurrentPage] = useState(1);
      const [itemsPerPage, setItemsPerPage] = useState(10);
      const [renderTime, setRenderTime] = useState(0);
    
      // Enhanced filtered and sorted employee list (same as before)
      const processedEmployees = useMemo(() => {
        const startTime = performance.now();
        
        let filtered = employees;
    
        // Apply search filter
        if (searchTerm) {
          const searchLower = searchTerm.toLowerCase();
          filtered = filtered.filter(employee => {
            const fullName = `${employee.firstName} ${employee.lastName}`.toLowerCase();
            const position = employee.position.toLowerCase();
            const department = employee.department.toLowerCase();
            const skills = employee.skills.join(' ').toLowerCase();
            const email = employee.email.toLowerCase();
            
            return fullName.includes(searchLower) ||
                   position.includes(searchLower) ||
                   department.includes(searchLower) ||
                   skills.includes(searchLower) ||
                   email.includes(searchLower);
          });
        }
    
        // Apply filters...
        if (selectedDepartment !== 'All Departments') {
          filtered = filtered.filter(employee => employee.department === selectedDepartment);
        }
        if (selectedLocation !== 'All Locations') {
          filtered = filtered.filter(employee => employee.location === selectedLocation);
        }
        if (selectedStatus !== 'All Status') {
          filtered = filtered.filter(employee => employee.status === selectedStatus);
        }
    
        // Apply sorting...
        const sorted = [...filtered].sort((a, b) => {
          let aValue, bValue;
          switch (sortBy) {
            case 'name':
              aValue = `${a.firstName} ${a.lastName}`.toLowerCase();
              bValue = `${b.firstName} ${b.lastName}`.toLowerCase();
              break;
            case 'position':
              aValue = a.position.toLowerCase();
              bValue = b.position.toLowerCase();
              break;
            case 'department':
              aValue = a.department.toLowerCase();
              bValue = b.department.toLowerCase();
              break;
            case 'location':
              aValue = a.location.toLowerCase();
              bValue = b.location.toLowerCase();
              break;
            case 'hireDate':
              aValue = new Date(a.hireDate);
              bValue = new Date(b.hireDate);
              break;
            case 'salary':
              aValue = a.salary;
              bValue = b.salary;
              break;
            default:
              aValue = a.id;
              bValue = b.id;
          }
    
          if (aValue < bValue) return sortOrder === 'asc' ? -1 : 1;
          if (aValue > bValue) return sortOrder === 'asc' ? 1 : -1;
          return 0;
        });
    
        const endTime = performance.now();
        setRenderTime(endTime - startTime);
    
        return sorted;
      }, [employees, searchTerm, selectedDepartment, selectedLocation, selectedStatus, sortBy, sortOrder]);
    
      // Paginated employees
      const paginatedEmployees = useMemo(() => {
        const startIndex = (currentPage - 1) * itemsPerPage;
        const endIndex = startIndex + itemsPerPage;
        return processedEmployees.slice(startIndex, endIndex);
      }, [processedEmployees, currentPage, itemsPerPage]);
    
      // Selection handlers
      const handleEmployeeSelection = (employeeId) => {
        setSelectedEmployees(prev => 
          prev.includes(employeeId) 
            ? prev.filter(id => id !== employeeId)
            : [...prev, employeeId]
        );
      };
    
      const handleSelectAll = () => {
        setSelectedEmployees(paginatedEmployees.map(emp => emp.id));
      };
    
      const handleDeselectAll = () => {
        setSelectedEmployees([]);
      };
    
      // Bulk action handlers
      const handleBulkExport = (selectedIds) => {
        const selectedData = employees.filter(emp => selectedIds.includes(emp.id));
        const csvContent = [
          ['Name', 'Position', 'Department', 'Email', 'Phone'].join(','),
          ...selectedData.map(emp => 
            [`${emp.firstName} ${emp.lastName}`, emp.position, emp.department, emp.email, emp.phone].join(',')
          )
        ].join('\n');
        
        const blob = new Blob([csvContent], { type: 'text/csv' });
        const url = window.URL.createObjectURL(blob);
        const a = document.createElement('a');
        a.href = url;
        a.download = 'selected_employees.csv';
        a.click();
        window.URL.revokeObjectURL(url);
        
        alert(`Exported ${selectedIds.length} employees to CSV file`);
      };
    
      const handleBulkStatusChange = (selectedIds, newStatus) => {
        alert(`In a real app, ${selectedIds.length} employees would be marked as ${newStatus}`);
      };
    
      // Reset selections when filters change
      React.useEffect(() => {
        setSelectedEmployees([]);
        setCurrentPage(1);
      }, [searchTerm, selectedDepartment, selectedLocation, selectedStatus]);
    
      // Functions
      const clearAllFilters = () => {
        setSearchTerm('');
        setSelectedDepartment('All Departments');
        setSelectedLocation('All Locations');
        setSelectedStatus('All Status');
        setSortBy('name');
        setSortOrder('asc');
        setSelectedEmployees([]);
        setCurrentPage(1);
      };
    
      const handlePageChange = (page) => {
        setCurrentPage(page);
        setSelectedEmployees([]); // Clear selections when changing pages
      };
    
      const handleItemsPerPageChange = (newItemsPerPage) => {
        setItemsPerPage(newItemsPerPage);
        setCurrentPage(1);
        setSelectedEmployees([]);
      };
    
      // Calculate statistics
      const stats = useMemo(() => ({
        total: processedEmployees.length,
        active: processedEmployees.filter(emp => emp.status === 'Active').length,
        departments: new Set(processedEmployees.map(emp => emp.department)).size
      }), [processedEmployees]);
    
      return (
        <div className="App">
          <PerformanceMonitor 
            employeeCount={paginatedEmployees.length}
            renderTime={renderTime}
          />
    
          <div className="employee-directory">
            <header className="directory-header">
              <h1>Employee Directory</h1>
              <p>Find and connect with your colleagues</p>
              <div className="stats">
                <div className="stat-item">
                  <span className="stat-number">{stats.total}</span>
                  <span className="stat-label">
                    {searchTerm || selectedDepartment !== 'All Departments' || 
                     selectedLocation !== 'All Locations' || selectedStatus !== 'All Status' 
                      ? 'Filtered Results' : 'Total Employees'}
                  </span>
                </div>
                <div className="stat-item">
                  <span className="stat-number">{stats.active}</span>
                  <span className="stat-label">Active</span>
                </div>
                <div className="stat-item">
                  <span className="stat-number">{stats.departments}</span>
                  <span className="stat-label">Departments</span>
                </div>
              </div>
            </header>
    
            <main className="directory-content">
              <SearchBar 
                searchTerm={searchTerm}
                setSearchTerm={setSearchTerm}
                placeholder="Search by name, position, department, skills, or email..."
              />
    
              <FilterControls
                departments={departments}
                locations={locations}
                selectedDepartment={selectedDepartment}
                setSelectedDepartment={setSelectedDepartment}
                selectedLocation={selectedLocation}
                setSelectedLocation={setSelectedLocation}
                selectedStatus={selectedStatus}
                setSelectedStatus={setSelectedStatus}
                onClearFilters={clearAllFilters}
              />
    
              <SortControls
                sortBy={sortBy}
                setSortBy={setSortBy}
                sortOrder={sortOrder}
                setSortOrder={setSortOrder}
              />
    
              <BulkActions
                selectedEmployees={selectedEmployees}
                onSelectAll={handleSelectAll}
                onDeselectAll={handleDeselectAll}
                totalEmployees={paginatedEmployees.length}
                onBulkExport={handleBulkExport}
                onBulkStatusChange={handleBulkStatusChange}
              />
    
              {(searchTerm || selectedDepartment !== 'All Departments' || 
                selectedLocation !== 'All Locations' || selectedStatus !== 'All Status') && (
                <div className="results-summary">
                  <p>
                    Showing {paginatedEmployees.length} of {processedEmployees.length} employees
                    {searchTerm && ` matching "${searchTerm}"`}
                    {' '}sorted by {sortBy} ({sortOrder === 'asc' ? 'ascending' : 'descending'})
                    {processedEmployees.length !== employees.length && 
                      ` (filtered from ${employees.length} total)`
                    }
                  </p>
                </div>
              )}
    
              <div className="employees-grid">
                {paginatedEmployees.map(employee => (
                  <EmployeeCard
                    key={employee.id}
                    employee={employee}
                    isSelected={selectedEmployees.includes(employee.id)}
                    onSelectionChange={handleEmployeeSelection}
                  />
                ))}
              </div>
    
              {processedEmployees.length === 0 && (
                <div className="no-employees">
                  <h3>No employees found</h3>
                  <p>
                    {searchTerm 
                      ? `No employees match your search for "${searchTerm}".`
                      : 'No employees match your current filters.'
                    }
                  </p>
                  <button className="clear-all-btn" onClick={clearAllFilters}>
                    Clear All Filters & Reset
                  </button>
                </div>
              )}
    
              <Pagination
                currentPage={currentPage}
                totalItems={processedEmployees.length}
                itemsPerPage={itemsPerPage}
                onPageChange={handlePageChange}
                onItemsPerPageChange={handleItemsPerPageChange}
              />
            </main>
          </div>
        </div>
      );
    }
    
    export default App;

Step 4.5: Test Advanced Features

  1. Save all files and test the advanced functionality:

    Pagination Tests:

    • Change items per page (5, 10, 20, 50) and observe page count updates
    • Navigate through pages using the pagination controls
    • Test pagination with different filter combinations
    • Verify page numbers display correctly with ellipsis for many pages
    • Test edge cases (single page, no results)

    Selection and Bulk Actions Tests:

    • Click individual checkboxes to select employees
    • Use the "Select all" checkbox to select all visible employees
    • Test the indeterminate state (some but not all selected)
    • Click "Actions" button to see bulk action menu
    • Test bulk export functionality (downloads CSV file)
    • Test bulk status change actions (shows alert)
    • Verify selection clears when changing pages or filters

    Integration Tests:

    • Apply filters, then test pagination
    • Sort data, then test selections
    • Search for employees, then test bulk actions
    • Change items per page with selections active
    • Test performance monitor with different page sizes
  2. Verify the following behaviors:

    • Selections are cleared when filters or pages change
    • Pagination correctly shows filtered results count
    • Bulk actions work only with selected items
    • Export functionality generates a proper CSV file
    • Performance metrics update with page size changes
    • All previous functionality (search, filter, sort) still works
  3. Test production scenarios:

    • Select employees across multiple criteria
    • Test with maximum items per page (50)
    • Try rapid filter changes and observe performance
    • Test bulk export with various selection combinations

Exercise 4 Summary: You have successfully implemented advanced list management features including pagination, bulk actions, and performance monitoring. You learned how to implement efficient pagination with smart page number display, create bulk selection and action systems, handle CSV export functionality, manage complex state interactions between features, optimize performance for large datasets, and provide comprehensive user feedback. This completes a production-ready employee directory with all the features users would expect in a modern web application.


Lab Summary

Congratulations! You have successfully completed the Employee Directory lab for Lesson 5. Throughout this comprehensive lab, you accomplished:

Mastered List Rendering: Implemented efficient rendering of dynamic employee data using map()
Applied React Keys: Used proper key props for optimal list performance and reconciliation
Built Search Functionality: Created real-time search across multiple employee fields
Implemented Filtering: Added dropdown filters for department, location, and status
Created Sorting System: Built interactive sorting by multiple criteria with visual feedback
Added Pagination: Implemented smart pagination with configurable page sizes
Built Bulk Actions: Created selection and bulk operation functionality
Optimized Performance: Used useMemo, proper keys, and performance monitoring

Key React Concepts Learned:

List Rendering Fundamentals:

  • Array.map(): Rendering arrays of data into React components
  • React Keys: Proper key usage for list item identification and performance
  • Component Composition: Building complex UIs from reusable components
  • Conditional Rendering: Displaying different content based on data states

Dynamic Data Manipulation:

  • Array.filter(): Filtering data based on multiple criteria
  • Array.sort(): Sorting data by different field types (strings, numbers, dates)
  • Search Implementation: Real-time search across multiple fields
  • State-Driven UI: UI that responds to data changes immediately

Performance Optimization:

  • useMemo: Preventing unnecessary recalculations of expensive operations
  • Proper React Keys: Optimizing reconciliation and re-renders
  • Pagination: Handling large datasets efficiently
  • Performance Monitoring: Measuring and tracking render performance

Advanced Features:

  • Bulk Operations: Multi-selection and batch actions
  • Export Functionality: CSV file generation and download
  • Complex State Management: Coordinating multiple related state variables
  • User Experience: Comprehensive feedback, loading states, and error handling

Real-World Skills Acquired:

  • Enterprise UI Patterns: Features common in business applications
  • Data Management: Handling, filtering, and presenting large datasets
  • User Experience Design: Intuitive interfaces with clear feedback
  • Performance Optimization: Building responsive applications that scale
  • File Operations: Generating and downloading data exports
  • Complex State Coordination: Managing interdependent features and state

Production-Ready Features Implemented:

  • Real-time search with instant results
  • Multi-criteria filtering with active filter indicators
  • Flexible sorting with visual direction indicators
  • Smart pagination with ellipsis and page size options
  • Bulk selection with select-all and indeterminate states
  • CSV export functionality for data portability
  • Performance monitoring for optimization insights
  • Responsive design for mobile and desktop users

Next Steps:

Your employee directory demonstrates mastery of list rendering, keys, and dynamic data manipulation in React. In future lessons, you'll build on these fundamentals to learn about forms, component patterns, and advanced React concepts. The skills you've gained here are directly applicable to building data-driven applications in professional development environments.

Time Completed: ~30 minutes total

Key Takeaways for Production Development:

  • Always use proper React keys for list items to ensure optimal performance
  • Implement useMemo for expensive calculations to prevent unnecessary re-renders
  • Provide comprehensive user feedback for all interactions and states
  • Design for scalability with pagination and virtual scrolling for large datasets
  • Consider user experience with bulk actions, clear filters, and intuitive navigation
  • Monitor performance and optimize bottlenecks in data processing
  • Structure components for reusability and maintainable code organization