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 основана на следующих принципах:
- Кэширование запросов - для оптимизации производительности используется кэширование часто запрашиваемых данных
- Параллельные запросы - для ускорения загрузки данных используются параллельные запросы с
Promise.all - Обработка ошибок - все запросы к API обернуты в try-catch блоки для корректной обработки ошибок
- Повторные попытки - при сбоях в сети реализован механизм повторных попыток
- Отложенная загрузка - часть данных загружается по мере необходимости для ускорения начальной загрузки
Адаптивный дизайн
Модуль управления идеями полностью адаптирован для работы на различных устройствах от мобильных телефонов до широкоформатных мониторов. Используются медиа-запросы для оптимизации отображения на разных экранах:
- На мобильных устройствах карточки идей отображаются в одну колонку
- На планшетах и настольных компьютерах карточки отображаются в несколько колонок
- Модальное окно детального просмотра адаптируется под размер экрана
- Фильтры и сортировка адаптируются для удобного использования на сенсорных экранах
Пустые состояния
Для обработки случаев, когда данные отсутствуют, используется компонент 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-системы. Это делает его ценным инструментом для принятия стратегических решений в области управления персоналом и развития команд.