import React, { useState, useEffect } from 'react'; import { Star, MessageCircle, ThumbsUp, ThumbsDown, Award, Search, UserCheck, TrendingUp, AlertCircle, Clock, Inbox } from 'lucide-react'; import { backendApi } from '../../services/apiClient'; interface OverallStats { total: number; completed: number; overdue: number; inProgress: number; completionRate: number; overdueRate: number; } interface EmployeeStats { employeeName: string; districtName: string | null; totalAssigned: number; totalCompleted: number; totalOverdue: number; performanceScore: number; } interface DistrictStats { districtId: string; districtName: string; managerName: string; totalApplications: number; totalCompleted: number; totalOverdue: number; completionRate: number; overdueRate: number; averageScore: number; } export const QualityControl: React.FC = () => { const [overallStats, setOverallStats] = useState(null); const [employeeStats, setEmployeeStats] = useState([]); const [districtStats, setDistrictStats] = useState([]); const [loading, setLoading] = useState(true); const [error, setError] = useState(null); useEffect(() => { const fetchStats = async () => { setLoading(true); setError(null); try { const [overallRes, employeesRes, districtsRes] = await Promise.all([ backendApi.getOverallPerformance(), backendApi.getEmployeePerformance(), backendApi.getDistrictPerformance(), ]); if (overallRes.success) { setOverallStats(overallRes.data); } if (employeesRes.success) { setEmployeeStats(employeesRes.data); } if (districtsRes.success) { setDistrictStats(districtsRes.data); } } catch (err) { console.error('[QualityControl] Ошибка загрузки статистики:', err); setError('Не удалось загрузить статистику производительности'); } finally { setLoading(false); } }; fetchStats(); }, []); if (loading) { return (

Загрузка статистики...

); } if (error) { return (

{error}

); } const bestEmployee = employeeStats.length > 0 ? employeeStats.reduce((best, current) => current.performanceScore > best.performanceScore ? current : best ) : null; // Рассчитываем общий CSAT на основе completionRate и overdueRate const csatScore = overallStats ? Math.max(1, Math.min(5, (overallStats.completionRate / 100) * 5 - (overallStats.overdueRate / 100) * 2)) : 0; return (
{/* Satisfaction Summary */}

Качество сервиса

Индекс производительности

{csatScore.toFixed(1)}

{[1,2,3,4,5].map(i => ( ))}
{overallStats && ( <> )}

Лучший мастер месяца

{bestEmployee ? (
{bestEmployee.employeeName.split(' ').map(n => n[0]).join('').substring(0, 2).toUpperCase()}

{bestEmployee.employeeName}

{bestEmployee.districtName && (

{bestEmployee.districtName}

)}

{bestEmployee.totalCompleted}

Выполнено

{bestEmployee.performanceScore.toFixed(1)}

Рейтинг

) : (

Нет данных

)}
{/* Статистика по участкам */} {districtStats.length > 0 && (

Статистика по участкам

{districtStats.map((district) => ( ))}
)} {/* Топ сотрудников */} {employeeStats.length > 0 && (

Рейтинг сотрудников

{employeeStats .sort((a, b) => b.performanceScore - a.performanceScore) .slice(0, 6) .map((employee, index) => ( ))}
)} {/* Общая статистика */} {overallStats && (

Общая статистика

} /> } color="emerald" /> } color="red" /> } color="blue" />
)}
); }; const RatingBar = ({ label, value, inverse = false }: { label: string; value: number; inverse?: boolean }) => { const displayValue = Math.max(0, Math.min(100, value)); const color = inverse ? (displayValue < 50 ? 'bg-red-500' : displayValue < 80 ? 'bg-amber-500' : 'bg-emerald-500') : 'bg-primary-500'; return (
{label} {displayValue.toFixed(1)}%
); }; const DistrictCard = ({ district }: { district: DistrictStats }) => (
{district.districtName}

{district.managerName}

{district.averageScore.toFixed(1)}

Рейтинг

Выполнено: {district.totalCompleted} / {district.totalApplications}
Просрочено: 0 ? 'text-red-600' : ''}>{district.totalOverdue}
); const EmployeeCard = ({ employee, rank }: { employee: EmployeeStats; rank: number }) => (
{rank}
{employee.employeeName}
{employee.districtName && (

{employee.districtName}

)}

{employee.performanceScore.toFixed(1)}

Рейтинг

Выполнено: {employee.totalCompleted} Просрочено: 0 ? 'text-red-600' : ''}>{employee.totalOverdue}
); const StatCard = ({ label, value, icon, color = 'primary' }: { label: string; value: number; icon: React.ReactNode; color?: 'primary' | 'emerald' | 'red' | 'blue'; }) => { const colorClasses = { primary: 'text-primary-500', emerald: 'text-emerald-500', red: 'text-red-500', blue: 'text-blue-500', }; return (
{icon}

{value}

{label}

); };