Темный режим

3.3. Управление идеями

Модуль «Управление идеями» представляет собой интеллектуальную систему для работы с автоматически генерируемыми инсайтами и рекомендациями на основе анализа данных опросов, метрик команд и другой информации. Этот модуль помогает HR-менеджерам и руководителям команд выявлять потенциальные области для улучшения и принимать обоснованные решения.

Основные компоненты модуля

  • Инсайты - автоматически генерируемые наблюдения и выводы на основе данных
  • Рекомендации - конкретные советы по улучшению показателей команды
  • Фильтры - инструменты для фильтрации и сортировки идей
  • Детальное представление - подробная информация об идеях и рекомендациях

Архитектура модуля управления идеями

Модуль управления идеями организован по следующей структуре:


src/pages/Ideas/
├── components/             # Компоненты пользовательского интерфейса
│   ├── Insights/           # Компоненты для работы с инсайтами
│   │   ├── InsightsList.tsx # Список инсайтов
│   │   └── InsightCard.tsx  # Карточка инсайта
│   ├── Advice/             # Компоненты для работы с рекомендациями
│   │   ├── AdviceList.tsx   # Список рекомендаций
│   │   └── AdviceCard.tsx   # Карточка рекомендации
│   ├── Tabs/               # Компоненты вкладок
│   │   └── TabList.tsx     # Список вкладок
│   ├── shared/             # Общие компоненты
│   │   ├── SortSelect.tsx  # Селектор для сортировки
│   │   ├── DetailModal.tsx # Модальное окно с деталями
│   │   ├── DepartmentFilter.tsx # Фильтр по отделам
│   │   └── MetricFilter.tsx # Фильтр по метрикам
│   └── EmptyStates/        # Компоненты для пустых состояний
│       └── IdeasEmpty.tsx  # Пустое состояние при отсутствии идей
├── hooks/                  # Пользовательские хуки
│   ├── useAIData.ts        # Хук для получения данных AI
│   └── useFilters.ts       # Хук для управления фильтрами
├── utils/                  # Утилиты и вспомогательные функции
│   ├── sortItems.ts        # Функции для сортировки элементов
│   └── filterItems.ts      # Функции для фильтрации элементов
├── constants/              # Константы
│   └── filterOptions.ts    # Опции фильтрации
└── index.tsx               # Главный компонент страницы идей
                

AI-инсайты

Система AI-инсайтов анализирует доступные данные и генерирует ценные наблюдения, которые могут быть использованы для улучшения работы команд и повышения вовлеченности сотрудников.

Компоненты для работы с инсайтами

  • InsightsList - компонент, отображающий список доступных инсайтов с возможностью фильтрации и сортировки
  • InsightCard - компонент, отображающий информацию об отдельном инсайте

Структура данных инсайта

Каждый инсайт содержит следующую информацию:

  • id - уникальный идентификатор инсайта
  • title - краткое описание инсайта
  • description - подробное описание инсайта
  • createdAt - дата и время создания инсайта
  • severity - уровень важности (LOW, MEDIUM, HIGH)
  • type - тип инсайта (TREND, ANOMALY, OPPORTUNITY)
  • metricId - связанная метрика
  • departmentId - связанный отдел (опционально)
  • teamId - связанная команда (опционально)
  • impact - потенциальное влияние инсайта
  • confidence - уровень достоверности инсайта (0-100%)

Компонент карточки инсайта

Компонент InsightCard отображает информацию об инсайте в виде интерактивной карточки:

// Пример компонента InsightCard из src/pages/Ideas/components/Insights/InsightCard.tsx
export const InsightCard = ({ insight, onClick }) => {
  // Определение цвета иконки в зависимости от важности инсайта
  const getSeverityColor = (severity) => {
    switch (severity) {
      case 'HIGH': return '#ef4444'; // Красный для высокой важности
      case 'MEDIUM': return '#f59e0b'; // Оранжевый для средней важности
      case 'LOW': return '#10b981'; // Зеленый для низкой важности
      default: return '#6366f1'; // Фиолетовый по умолчанию
    }
  };
  
  // Определение иконки в зависимости от типа инсайта
  const getInsightIcon = (type) => {
    switch (type) {
      case 'TREND': return 'trend-icon';
      case 'ANOMALY': return 'anomaly-icon';
      case 'OPPORTUNITY': return 'opportunity-icon';
      default: return 'insight-icon';
    }
  };
  
  // Форматирование даты
  const formatDate = (dateString) => {
    const date = new Date(dateString);
    return new Intl.DateTimeFormat('ru-RU', { 
      day: 'numeric', 
      month: 'short', 
      year: 'numeric' 
    }).format(date);
  };
  
  return (
    <div 
      className="insight-card"
      onClick={() => onClick(insight)}
    >
      <div 
        className={`insight-icon ${getInsightIcon(insight.type)}`}
        style={{ backgroundColor: getSeverityColor(insight.severity) }}
      >
        {/* Иконка инсайта */}
      </div>
      
      <div className="insight-content">
        <h3 className="insight-title">{insight.title}</h3>
        <p className="insight-description">{insight.description}</p>
        
        <div className="insight-meta">
          <span className="insight-date">{formatDate(insight.createdAt)}</span>
          <span className="insight-confidence">
            Достоверность: {insight.confidence}%
          </span>
        </div>
        
        <div className="insight-tags">
          {insight.metricId && (
            <span className="metric-tag">
              {insight.metricName || 'Метрика'}
            </span>
          )}
          
          {insight.departmentId && (
            <span className="department-tag">
              {insight.departmentName || 'Отдел'}
            </span>
          )}
          
          <span className={`severity-tag ${insight.severity.toLowerCase()}`}>
            {insight.severity === 'HIGH' ? 'Высокая важность' : 
              insight.severity === 'MEDIUM' ? 'Средняя важность' : 'Низкая важность'}
          </span>
        </div>
      </div>
      
      <div className="insight-action">
        <span className="action-icon">></span>
      </div>
    </div>
  );
};

Компонент списка инсайтов

Компонент InsightsList отображает список всех доступных инсайтов и предоставляет возможности для их фильтрации и сортировки:

// Пример компонента InsightsList из src/pages/Ideas/components/Insights/InsightsList.tsx
export const InsightsList = ({ insights, filters, onFilterChange, onSortChange, sortConfig, onInsightClick }) => {
  // Применение фильтров и сортировки к инсайтам
  const filteredInsights = useMemo(() => {
    // Фильтруем инсайты с помощью функции filterItems
    return filterItems(insights, filters);
  }, [insights, filters]);
  
  const sortedInsights = useMemo(() => {
    // Сортируем отфильтрованные инсайты
    return sortItems(filteredInsights, sortConfig);
  }, [filteredInsights, sortConfig]);
  
  // Если нет инсайтов после фильтрации
  if (filteredInsights.length === 0) {
    return (
      <div className="insights-empty-state">
        <p>Нет инсайтов, соответствующих выбранным фильтрам</p>
        <button 
          className="reset-filters-button"
          onClick={() => onFilterChange('reset', null)}
        >
          Сбросить фильтры
        </button>
      </div>
    );
  }
  
  return (
    <div className="insights-list">
      {/* Заголовок и информация о количестве */}
      <div className="insights-header">
        <h2>Инсайты ({sortedInsights.length})</h2>
        
        <div className="insights-actions">
          <SortSelect 
            value={sortConfig.field}
            onSortChange={(field) => onSortChange(field)}
            options={[
              { value: 'date', label: 'По дате' },
              { value: 'severity', label: 'По важности' },
              { value: 'confidence', label: 'По достоверности' }
            ]}
          />
        </div>
      </div>
      
      {/* Список инсайтов */}
      <div className="insights-cards">
        {sortedInsights.map(insight => (
          <InsightCard 
            key={insight.id}
            insight={insight}
            onClick={onInsightClick}
          />
        ))}
      </div>
    </div>
  );
};

AI-рекомендации

Система AI-рекомендаций предоставляет конкретные советы и действия, которые могут быть предприняты для улучшения различных показателей команды. Рекомендации основаны на анализе данных опросов, метрик и лучших практик управления командами.

Компоненты для работы с рекомендациями

  • AdviceList - компонент, отображающий список рекомендаций с возможностью фильтрации и сортировки
  • AdviceCard - компонент, отображающий информацию об отдельной рекомендации

Структура данных рекомендации

Каждая рекомендация содержит следующую информацию:

  • id - уникальный идентификатор рекомендации
  • title - краткое описание рекомендации
  • description - подробное описание рекомендации
  • createdAt - дата и время создания рекомендации
  • severity - уровень приоритета (LOW, MEDIUM, HIGH)
  • type - тип рекомендации (PERFORMANCE_IMPROVEMENT, ENGAGEMENT_BOOST, COMMUNICATION_ENHANCEMENT, etc.)
  • metricId - связанная метрика
  • departmentId - связанный отдел (опционально)
  • teamId - связанная команда (опционально)
  • steps - конкретные шаги для реализации рекомендации
  • potentialImpact - потенциальное влияние при внедрении рекомендации
  • implementationDifficulty - сложность внедрения (EASY, MEDIUM, HARD)

Компонент карточки рекомендации

Компонент AdviceCard отображает информацию о рекомендации в виде интерактивной карточки:

// Пример компонента AdviceCard из src/pages/Ideas/components/Advice/AdviceCard.tsx
export const AdviceCard = ({ advice, onClick }) => {
  // Определение цвета иконки в зависимости от приоритета рекомендации
  const getPriorityColor = (severity) => {
    switch (severity) {
      case 'HIGH': return '#ef4444'; // Красный для высокого приоритета
      case 'MEDIUM': return '#f59e0b'; // Оранжевый для среднего приоритета
      case 'LOW': return '#10b981'; // Зеленый для низкого приоритета
      default: return '#6366f1'; // Фиолетовый по умолчанию
    }
  };
  
  // Определение иконки в зависимости от типа рекомендации
  const getAdviceIcon = (type) => {
    switch (type) {
      case 'PERFORMANCE_IMPROVEMENT': return 'performance-icon';
      case 'ENGAGEMENT_BOOST': return 'engagement-icon';
      case 'COMMUNICATION_ENHANCEMENT': return 'communication-icon';
      default: return 'advice-icon';
    }
  };
  
  // Форматирование даты
  const formatDate = (dateString) => {
    const date = new Date(dateString);
    return new Intl.DateTimeFormat('ru-RU', { 
      day: 'numeric', 
      month: 'short', 
      year: 'numeric' 
    }).format(date);
  };
  
  // Перевод сложности внедрения
  const getDifficultyLabel = (difficulty) => {
    switch (difficulty) {
      case 'HARD': return 'Сложно';
      case 'MEDIUM': return 'Средне';
      case 'EASY': return 'Легко';
      default: return 'Не определено';
    }
  };
  
  return (
    <div 
      className="advice-card"
      onClick={() => onClick(advice)}
    >
      <div 
        className={`advice-icon ${getAdviceIcon(advice.type)}`}
        style={{ backgroundColor: getPriorityColor(advice.severity) }}
      >
        {/* Иконка рекомендации */}
      </div>
      
      <div className="advice-content">
        <h3 className="advice-title">{advice.title}</h3>
        <p className="advice-description">{advice.description}</p>
        
        <div className="advice-meta">
          <span className="advice-date">{formatDate(advice.createdAt)}</span>
          <span className="advice-impact">
            Потенциальное влияние: {advice.potentialImpact}
          </span>
          <span className="advice-difficulty">
            Сложность: {getDifficultyLabel(advice.implementationDifficulty)}
          </span>
        </div>
        
        <div className="advice-tags">
          {advice.metricId && (
            <span className="metric-tag">
              {advice.metricName || 'Метрика'}
            </span>
          )}
          
          {advice.departmentId && (
            <span className="department-tag">
              {advice.departmentName || 'Отдел'}
            </span>
          )}
          
          <span className={`priority-tag ${advice.severity.toLowerCase()}`}>
            {advice.severity === 'HIGH' ? 'Высокий приоритет' : 
              advice.severity === 'MEDIUM' ? 'Средний приоритет' : 'Низкий приоритет'}
          </span>
        </div>
      </div>
      
      <div className="advice-action">
        <span className="action-icon">></span>
      </div>
    </div>
  );
};

Компонент списка рекомендаций

Компонент AdviceList отображает список всех доступных рекомендаций и предоставляет возможности для их фильтрации и сортировки:

// Пример компонента AdviceList из src/pages/Ideas/components/Advice/AdviceList.tsx
export const AdviceList = ({ advices, filters, onFilterChange, onSortChange, sortConfig, onAdviceClick }) => {
  // Применение фильтров и сортировки к рекомендациям
  const filteredAdvices = useMemo(() => {
    // Фильтруем рекомендации с помощью функции filterItems
    return filterItems(advices, filters);
  }, [advices, filters]);
  
  const sortedAdvices = useMemo(() => {
    // Сортируем отфильтрованные рекомендации
    return sortItems(filteredAdvices, sortConfig);
  }, [filteredAdvices, sortConfig]);
  
  // Если нет рекомендаций после фильтрации
  if (filteredAdvices.length === 0) {
    return (
      <div className="advices-empty-state">
        <p>Нет рекомендаций, соответствующих выбранным фильтрам</p>
        <button 
          className="reset-filters-button"
          onClick={() => onFilterChange('reset', null)}
        >
          Сбросить фильтры
        </button>
      </div>
    );
  }
  
  return (
    <div className="advices-list">
      {/* Заголовок и информация о количестве */}
      <div className="advices-header">
        <h2>Рекомендации ({sortedAdvices.length})</h2>
        
        <div className="advices-actions">
          <SortSelect 
            value={sortConfig.field}
            onSortChange={(field) => onSortChange(field)}
            options={[
              { value: 'date', label: 'По дате' },
              { value: 'severity', label: 'По приоритету' },
              { value: 'impact', label: 'По влиянию' },
              { value: 'difficulty', label: 'По сложности' }
            ]}
          />
        </div>
      </div>
      
      {/* Список рекомендаций */}
      <div className="advices-cards">
        {sortedAdvices.map(advice => (
          <AdviceCard 
            key={advice.id}
            advice={advice}
            onClick={onAdviceClick}
          />
        ))}
      </div>
    </div>
  );
};

Фильтрация и сортировка

Модуль управления идеями предоставляет мощные инструменты для фильтрации и сортировки инсайтов и рекомендаций, позволяя пользователям быстро находить нужную информацию.

Компоненты фильтрации

  • DepartmentFilter - фильтр по отделам
  • MetricFilter - фильтр по метрикам
  • SortSelect - селектор для выбора параметра сортировки

Хук useFilters

Хук useFilters инкапсулирует логику управления фильтрами и сортировкой:

// Пример хука useFilters из src/pages/Ideas/hooks/useFilters.ts
export const useFilters = (initialFilters = {}) => {
  const [filters, setFilters] = useState(initialFilters);
  const [sortConfig, setSortConfig] = useState({
    field: 'date',
    direction: 'desc'
  });
  
  // Функция для обновления фильтров
  const updateFilter = (filterType, value) => {
    if (filterType === 'reset') {
      // Сброс всех фильтров
      setFilters(initialFilters);
      return;
    }
    
    setFilters(prevFilters => ({
      ...prevFilters,
      [filterType]: value
    }));
  };
  
  // Функция для обновления сортировки
  const updateSort = (field) => {
    setSortConfig(prevConfig => {
      // Если поле то же самое, меняем направление
      if (prevConfig.field === field) {
        return {
          field,
          direction: prevConfig.direction === 'asc' ? 'desc' : 'asc'
        };
      }
      
      // Если поле новое, устанавливаем направление по умолчанию
      return {
        field,
        direction: 'desc'
      };
    });
  };
  
  return {
    filters,
    updateFilter,
    sortConfig,
    updateSort
  };
};

Функции для фильтрации элементов

Модуль использует набор функций для фильтрации элементов по различным критериям:

// Пример функций фильтрации из src/pages/Ideas/utils/filterItems.ts
export const filterItems = (items, filters) => {
  return items.filter(item => {
    // Фильтрация по отделу
    if (filters.department && item.departmentId !== filters.department) {
      return false;
    }
    
    // Фильтрация по метрике
    if (filters.metric && item.metricId !== filters.metric) {
      return false;
    }
    
    // Фильтрация по важности
    if (filters.severity && filters.severity.length > 0) {
      if (!filters.severity.includes(item.severity)) {
        return false;
      }
    }
    
    // Фильтрация по типу
    if (filters.type && filters.type.length > 0) {
      if (!filters.type.includes(item.type)) {
        return false;
      }
    }
    
    // Фильтрация по дате
    if (filters.dateRange) {
      const itemDate = new Date(item.createdAt);
      if (filters.dateRange.start && itemDate < filters.dateRange.start) {
        return false;
      }
      if (filters.dateRange.end && itemDate > filters.dateRange.end) {
        return false;
      }
    }
    
    // Фильтрация по поисковому запросу
    if (filters.search && filters.search.trim() !== '') {
      const searchLower = filters.search.toLowerCase().trim();
      const titleMatches = item.title.toLowerCase().includes(searchLower);
      const descriptionMatches = item.description.toLowerCase().includes(searchLower);
      
      if (!titleMatches && !descriptionMatches) {
        return false;
      }
    }
    
    return true;
  });
};

Функции для сортировки элементов

Для сортировки элементов используются функции, определенные в утилите sortItems.ts:

// Пример функций сортировки из src/pages/Ideas/utils/sortItems.ts
export const sortItems = (items, sortConfig) => {
  const { field, direction } = sortConfig;
  
  return [...items].sort((a, b) => {
    let valueA, valueB;
    
    switch (field) {
      case 'date':
        valueA = new Date(a.createdAt).getTime();
        valueB = new Date(b.createdAt).getTime();
        break;
        
      case 'severity':
        // Преобразование текстовых значений важности в числовые
        valueA = getSeverityValue(a.severity);
        valueB = getSeverityValue(b.severity);
        break;
        
      case 'confidence':
        valueA = a.confidence || 0;
        valueB = b.confidence || 0;
        break;
        
      case 'impact':
        valueA = getImpactValue(a.potentialImpact);
        valueB = getImpactValue(b.potentialImpact);
        break;
        
      case 'difficulty':
        valueA = getDifficultyValue(a.implementationDifficulty);
        valueB = getDifficultyValue(b.implementationDifficulty);
        break;
        
      default:
        valueA = a[field];
        valueB = b[field];
    }
    
    if (valueA === valueB) {
      // При равных значениях сортируем по дате
      return direction === 'asc' 
        ? new Date(a.createdAt).getTime() - new Date(b.createdAt).getTime()
        : new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime();
    }
    
    return direction === 'asc' 
      ? (valueA < valueB ? -1 : 1)
      : (valueA > valueB ? -1 : 1);
  });
};

// Вспомогательные функции для преобразования значений

// Получение числового значения для уровня важности
const getSeverityValue = (severity) => {
  switch (severity) {
    case 'HIGH': return 3;
    case 'MEDIUM': return 2;
    case 'LOW': return 1;
    default: return 0;
  }
};

// Получение числового значения для потенциального влияния
const getImpactValue = (impact) => {
  if (!impact) return 0;
  
  // Если влияние уже представлено числом
  if (typeof impact === 'number') return impact;
  
  // Если влияние представлено строкой (например, "HIGH")
  switch (impact) {
    case 'HIGH': return 3;
    case 'MEDIUM': return 2;
    case 'LOW': return 1;
    default: return 0;
  }
};

// Получение числового значения для сложности внедрения
const getDifficultyValue = (difficulty) => {
  switch (difficulty) {
    case 'HARD': return 3;
    case 'MEDIUM': return 2;
    case 'EASY': return 1;
    default: return 0;
  }
};

Детальное представление

Модуль управления идеями предоставляет возможность детального просмотра инсайтов и рекомендаций в модальном окне.

Компонент DetailModal

Компонент DetailModal отображает подробную информацию о выбранном инсайте или рекомендации:

// Пример компонента DetailModal из src/pages/Ideas/components/shared/DetailModal.tsx
export const DetailModal = ({ item, itemType, onClose }) => {
  const isInsight = itemType === 'insight';
  
  // Форматирование даты
  const formatDate = (dateString) => {
    const date = new Date(dateString);
    return new Intl.DateTimeFormat('ru-RU', { 
      day: 'numeric', 
      month: 'long', 
      year: 'numeric',
      hour: '2-digit',
      minute: '2-digit'
    }).format(date);
  };
  
  return (
    <div className="detail-modal-overlay" onClick={onClose}>
      <div className="detail-modal-content" onClick={e => e.stopPropagation()}>
        <button className="close-button" onClick={onClose}>×</button>
        
        <div className="detail-header">
          <h2>{isInsight ? 'Инсайт' : 'Рекомендация'}: {item.title}</h2>
          <p className="detail-date">Создано: {formatDate(item.createdAt)}</p>
        </div>
        
        <div className="detail-body">
          <div className="detail-section">
            <h3>Описание</h3>
            <p>{item.description}</p>
          </div>
          
          {isInsight && (
            <div className="detail-section">
              <h3>Анализ</h3>
              <p>
                <strong>Достоверность:</strong> {item.confidence}%
              </p>
              <p>
                <strong>Потенциальное влияние:</strong> {item.impact}
              </p>
              {item.analysisDetails && (
                <div className="analysis-details">
                  <p>{item.analysisDetails}</p>
                </div>
              )}
            </div>
          )}
          
          {!isInsight && (
            <div className="detail-section">
              <h3>Шаги внедрения</h3>
              <ol className="implementation-steps">
                {item.steps && item.steps.map((step, index) => (
                  <li key={index}>{step}</li>
                ))}
              </ol>
              
              <div className="implementation-info">
                <div className="info-item">
                  <h4>Сложность внедрения:</h4>
                  <p className={`difficulty ${item.implementationDifficulty?.toLowerCase()}`}>
                    {item.implementationDifficulty === 'EASY' ? 'Легко' : 
                     item.implementationDifficulty === 'MEDIUM' ? 'Средне' : 'Сложно'}
                  </p>
                </div>
                
                <div className="info-item">
                  <h4>Потенциальное влияние:</h4>
                  <p>{item.potentialImpact}</p>
                </div>
              </div>
            </div>
          )}
          
          <div className="detail-section">
            <h3>Связанная информация</h3>
            
            {item.metricId && (
              <div className="related-item">
                <h4>Метрика:</h4>
                <p>{item.metricName || 'Не указано'}</p>
              </div>
            )}
            
            {item.departmentId && (
              <div className="related-item">
                <h4>Отдел:</h4>
                <p>{item.departmentName || 'Не указано'}</p>
              </div>
            )}
            
            {item.teamId && (
              <div className="related-item">
                <h4>Команда:</h4>
                <p>{item.teamName || 'Не указано'}</p>
              </div>
            )}
          </div>
        </div>
        
        <div className="detail-footer">
          {isInsight && item.relatedAdviceIds && item.relatedAdviceIds.length > 0 && (
            <div className="related-advice">
              <h3>Связанные рекомендации</h3>
              <ul>
                {item.relatedAdviceIds.map((adviceId, index) => (
                  <li key={adviceId}>
                    Рекомендация {index + 1}
                  </li>
                ))}
              </ul>
            </div>
          )}
          
          {!isInsight && item.relatedInsightIds && item.relatedInsightIds.length > 0 && (
            <div className="related-insights">
              <h3>Связанные инсайты</h3>
              <ul>
                {item.relatedInsightIds.map((insightId, index) => (
                  <li key={insightId}>
                    Инсайт {index + 1}
                  </li>
                ))}
              </ul>
            </div>
          )}
          
          <div className="action-buttons">
            {!isInsight && (
              <button className="implement-button">
                Внедрить рекомендацию
              </button>
            )}
            <button className="secondary-button" onClick={onClose}>
              Закрыть
            </button>
          </div>
        </div>
      </div>
    </div>
  );
};

Получение данных AI

Для получения данных AI-инсайтов и рекомендаций используется хук useAIData, который обращается к API для получения актуальной информации.

Хук useAIData

Хук useAIData выполняет запросы к API и обрабатывает полученные данные:

// Пример хука useAIData из src/pages/Ideas/hooks/useAIData.ts
export const useAIData = () => {
  const [insights, setInsights] = useState([]);
  const [advice, setAdvice] = useState([]);
  const [isLoading, setIsLoading] = useState(true);
  const [error, setError] = useState(null);
  
  // Получение данных при монтировании компонента
  useEffect(() => {
    const fetchData = async () => {
      try {
        setIsLoading(true);
        
        // Параллельное выполнение запросов для оптимизации
        const [insightsResponse, adviceResponse] = await Promise.all([
          aiService.getInsights(),
          aiService.getAdvice()
        ]);
        
        setInsights(insightsResponse);
        setAdvice(adviceResponse);
        setError(null);
      } catch (err) {
        console.error('Error fetching AI data:', err);
        setError('Произошла ошибка при загрузке данных AI. Пожалуйста, попробуйте позже.');
      } finally {
        setIsLoading(false);
      }
    };
    
    fetchData();
  }, []);
  
  // Обновление данных с сервера
  const refreshData = async () => {
    try {
      setIsLoading(true);
      
      const [insightsResponse, adviceResponse] = await Promise.all([
        aiService.getInsights(),
        aiService.getAdvice()
      ]);
      
      setInsights(insightsResponse);
      setAdvice(adviceResponse);
      setError(null);
    } catch (err) {
      console.error('Error refreshing AI data:', err);
      setError('Произошла ошибка при обновлении данных AI. Пожалуйста, попробуйте позже.');
    } finally {
      setIsLoading(false);
    }
  };
  
  return {
    insights,
    advice,
    isLoading,
    error,
    refreshData
  };
};

Главный компонент Ideas

Основной компонент Ideas объединяет все описанные выше компоненты и служит точкой входа в модуль управления идеями:

// Пример основного компонента Ideas из src/pages/Ideas/index.tsx
export const Ideas = () => {
  const [activeTab, setActiveTab] = useState('insights');
  const [selectedItem, setSelectedItem] = useState(null);
  const [selectedItemType, setSelectedItemType] = useState(null);
  
  // Получение данных AI с помощью хука
  const { insights, advice, isLoading, error, refreshData } = useAIData();
  
  // Управление фильтрами
  const { filters, updateFilter, sortConfig, updateSort } = useFilters({
    department: null,
    metric: null,
    severity: [],
    type: [],
    dateRange: null,
    search: ''
  });
  
  // Обработчик клика по инсайту
  const handleInsightClick = (insight) => {
    setSelectedItem(insight);
    setSelectedItemType('insight');
  };
  
  // Обработчик клика по рекомендации
  const handleAdviceClick = (advice) => {
    setSelectedItem(advice);
    setSelectedItemType('advice');
  };
  
  // Закрытие модального окна
  const handleCloseModal = () => {
    setSelectedItem(null);
    setSelectedItemType(null);
  };
  
  // Обработка состояний загрузки и ошибок
  if (isLoading) {
    return <div className="loading-state">Загрузка данных...</div>;
  }
  
  if (error) {
    return (
      <div className="error-state">
        <p>{error}</p>
        <button onClick={refreshData}>Повторить попытку</button>
      </div>
    );
  }
  
  if (!insights.length && !advice.length) {
    return <IdeasEmpty />;
  }
  
  return (
    <div className="ideas-container">
      <div className="ideas-header">
        <h1>Управление идеями</h1>
        
        <div className="refresh-button" onClick={refreshData}>
          Обновить данные
        </div>
      </div>
      
      <div className="ideas-filters">
        <div className="search-filter">
          <input 
            type="text"
            placeholder="Поиск..."
            value={filters.search || ''}
            onChange={(e) => updateFilter('search', e.target.value)}
          />
        </div>
        
        <div className="filter-group">
          <DepartmentFilter 
            selectedDepartment={filters.department}
            onDepartmentChange={(departmentId) => updateFilter('department', departmentId)}
          />
          
          <MetricFilter 
            selectedMetric={filters.metric}
            onMetricChange={(metricId) => updateFilter('metric', metricId)}
          />
          
          <button 
            className="reset-filters"
            onClick={() => updateFilter('reset', null)}
          >
            Сбросить фильтры
          </button>
        </div>
      </div>
      
      <div className="ideas-tabs">
        <TabList 
          tabs={[
            { id: 'insights', label: 'Инсайты' },
            { id: 'advice', label: 'Рекомендации' }
          ]}
          activeTab={activeTab}
          onTabChange={setActiveTab}
        />
      </div>
      
      <div className="ideas-content">
        {activeTab === 'insights' && (
          <InsightsList 
            insights={insights}
            filters={filters}
            onFilterChange={updateFilter}
            sortConfig={sortConfig}
            onSortChange={updateSort}
            onInsightClick={handleInsightClick}
          />
        )}
        
        {activeTab === 'advice' && (
          <AdviceList 
            advices={advice}
            filters={filters}
            onFilterChange={updateFilter}
            sortConfig={sortConfig}
            onSortChange={updateSort}
            onAdviceClick={handleAdviceClick}
          />
        )}
      </div>
      
      {selectedItem && selectedItemType && (
        <DetailModal 
          item={selectedItem}
          itemType={selectedItemType}
          onClose={handleCloseModal}
        />
      )}
    </div>
  );
};

Интеграция с API

Модуль управления идеями интегрируется с несколькими API-сервисами для получения данных:

  • insightsApi - API для получения и обработки инсайтов
  • aiAdviceApi - API для получения AI-рекомендаций
  • teamsApi - API для получения информации о командах и отделах
  • metricsApi - API для получения информации о метриках

Принципы API интеграции

Интеграция с API основана на следующих принципах:

  1. Кэширование запросов - для оптимизации производительности используется кэширование часто запрашиваемых данных
  2. Параллельные запросы - для ускорения загрузки данных используются параллельные запросы с Promise.all
  3. Обработка ошибок - все запросы к API обернуты в try-catch блоки для корректной обработки ошибок
  4. Повторные попытки - при сбоях в сети реализован механизм повторных попыток
  5. Отложенная загрузка - часть данных загружается по мере необходимости для ускорения начальной загрузки

Адаптивный дизайн

Модуль управления идеями полностью адаптирован для работы на различных устройствах от мобильных телефонов до широкоформатных мониторов. Используются медиа-запросы для оптимизации отображения на разных экранах:

  • На мобильных устройствах карточки идей отображаются в одну колонку
  • На планшетах и настольных компьютерах карточки отображаются в несколько колонок
  • Модальное окно детального просмотра адаптируется под размер экрана
  • Фильтры и сортировка адаптируются для удобного использования на сенсорных экранах

Пустые состояния

Для обработки случаев, когда данные отсутствуют, используется компонент IdeasEmpty:

// Пример компонента IdeasEmpty из src/pages/Ideas/components/EmptyStates/IdeasEmpty.tsx
export const IdeasEmpty = () => {
  return (
    <div className="ideas-empty-container">
      <div className="empty-illustration">
        {/* Иллюстрация пустого состояния */}
      </div>
      
      <h2>Идеи и рекомендации пока отсутствуют</h2>
      
      <p>
        После того, как вы начнете использовать систему, здесь появятся инсайты и рекомендации.
        Инсайты автоматически генерируются на основе данных опросов и метрик команд.
      </p>
      
      <div className="empty-actions">
        <a href="/surveys" className="create-survey-link">
          Создать новый опрос
        </a>
        
        <a href="/dashboard" className="go-dashboard-link">
          Перейти к дашборду
        </a>
      </div>
    </div>
  );
};

Выводы

Модуль управления идеями HRoom представляет собой мощный инструмент для работы с AI-генерируемыми инсайтами и рекомендациями. Он позволяет HR-менеджерам и руководителям команд быстро выявлять потенциальные области для улучшения и принимать обоснованные решения на основе аналитических данных.

Ключевые преимущества модуля управления идеями:

  • Автоматическая генерация инсайтов на основе данных опросов и метрик
  • Конкретные, практически применимые рекомендации для улучшения показателей команды
  • Гибкие возможности фильтрации и сортировки
  • Детальное представление информации с возможностью перехода к связанным данным
  • Адаптивный дизайн для работы на различных устройствах
  • Интеграция с другими модулями системы для создания целостного опыта работы

Модуль постоянно развивается, улучшая качество генерируемых инсайтов и рекомендаций с накоплением данных и обучением AI-системы. Это делает его ценным инструментом для принятия стратегических решений в области управления персоналом и развития команд.