Spaces:
Running
Running
| <html lang="ru"> | |
| <head> | |
| <meta charset="UTF-8"> | |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
| <title>Mindia AI - Аналитика инцидентов</title> | |
| <script crossorigin src="https://unpkg.com/react@18/umd/react.production.min.js"></script> | |
| <script crossorigin src="https://unpkg.com/react-dom@18/umd/react-dom.production.min.js"></script> | |
| <script src="https://unpkg.com/@babel/standalone/babel.min.js"></script> | |
| <script src="https://cdn.tailwindcss.com"></script> | |
| <style> | |
| * { | |
| margin: 0; | |
| padding: 0; | |
| box-sizing: border-box; | |
| } | |
| body { | |
| font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', sans-serif; | |
| } | |
| @keyframes fadeInUp { | |
| from { | |
| opacity: 0; | |
| transform: translateY(20px); | |
| } | |
| to { | |
| opacity: 1; | |
| transform: translateY(0); | |
| } | |
| } | |
| .fade-in-up { | |
| animation: fadeInUp 0.6s ease-out forwards; | |
| } | |
| .stagger-1 { animation-delay: 0.1s; } | |
| .stagger-2 { animation-delay: 0.2s; } | |
| .stagger-3 { animation-delay: 0.3s; } | |
| .stagger-4 { animation-delay: 0.4s; } | |
| </style> | |
| </head> | |
| <body> | |
| <div id="root"></div> | |
| <script type="text/babel"> | |
| const { useState, useEffect, useRef } = React; | |
| // Icons | |
| const AlertTriangle = ({ size = 24, className = "" }) => ( | |
| <svg width={size} height={size} viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" className={className}> | |
| <path d="M10.29 3.86L1.82 18a2 2 0 0 0 1.71 3h16.94a2 2 0 0 0 1.71-3L13.71 3.86a2 2 0 0 0-3.42 0z"/> | |
| <line x1="12" y1="9" x2="12" y2="13"/> | |
| <line x1="12" y1="17" x2="12.01" y2="17"/> | |
| </svg> | |
| ); | |
| const Brain = ({ size = 24, className = "" }) => ( | |
| <svg width={size} height={size} viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" className={className}> | |
| <path d="M9.5 2A2.5 2.5 0 0 1 12 4.5v15a2.5 2.5 0 0 1-4.96.44 2.5 2.5 0 0 1-2.96-3.08 3 3 0 0 1-.34-5.58 2.5 2.5 0 0 1 1.32-4.24 2.5 2.5 0 0 1 1.98-3A2.5 2.5 0 0 1 9.5 2Z"/> | |
| <path d="M14.5 2A2.5 2.5 0 0 0 12 4.5v15a2.5 2.5 0 0 0 4.96.44 2.5 2.5 0 0 0 2.96-3.08 3 3 0 0 0 .34-5.58 2.5 2.5 0 0 0-1.32-4.24 2.5 2.5 0 0 0-1.98-3A2.5 2.5 0 0 0 14.5 2Z"/> | |
| </svg> | |
| ); | |
| const TrendingDown = ({ size = 24, className = "" }) => ( | |
| <svg width={size} height={size} viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" className={className}> | |
| <polyline points="23 18 13.5 8.5 8.5 13.5 1 6"/> | |
| <polyline points="17 18 23 18 23 12"/> | |
| </svg> | |
| ); | |
| const BarChart = ({ size = 24, className = "" }) => ( | |
| <svg width={size} height={size} viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" className={className}> | |
| <line x1="12" y1="20" x2="12" y2="10"/> | |
| <line x1="18" y1="20" x2="18" y2="4"/> | |
| <line x1="6" y1="20" x2="6" y2="16"/> | |
| </svg> | |
| ); | |
| const FileText = ({ size = 24, className = "" }) => ( | |
| <svg width={size} height={size} viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" className={className}> | |
| <path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"/> | |
| <polyline points="14 2 14 8 20 8"/> | |
| </svg> | |
| ); | |
| const Lightbulb = ({ size = 24, className = "" }) => ( | |
| <svg width={size} height={size} viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" className={className}> | |
| <path d="M15 14c.2-1 .7-1.7 1.5-2.5 1-.9 1.5-2.2 1.5-3.5A6 6 0 0 0 6 8c0 1 .2 2.2 1.5 3.5.7.7 1.3 1.5 1.5 2.5"/> | |
| <path d="M9 18h6"/> | |
| <path d="M10 22h4"/> | |
| </svg> | |
| ); | |
| const Calendar = ({ size = 24, className = "" }) => ( | |
| <svg width={size} height={size} viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" className={className}> | |
| <rect x="3" y="4" width="18" height="18" rx="2" ry="2"/> | |
| <line x1="16" y1="2" x2="16" y2="6"/> | |
| <line x1="8" y1="2" x2="8" y2="6"/> | |
| <line x1="3" y1="10" x2="21" y2="10"/> | |
| </svg> | |
| ); | |
| const Target = ({ size = 24, className = "" }) => ( | |
| <svg width={size} height={size} viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" className={className}> | |
| <circle cx="12" cy="12" r="10"/> | |
| <circle cx="12" cy="12" r="6"/> | |
| <circle cx="12" cy="12" r="2"/> | |
| </svg> | |
| ); | |
| const CheckCircle = ({ size = 24, className = "" }) => ( | |
| <svg width={size} height={size} viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" className={className}> | |
| <path d="M22 11.08V12a10 10 0 1 1-5.93-9.14"/> | |
| <polyline points="22 4 12 14.01 9 11.01"/> | |
| </svg> | |
| ); | |
| const IncidentAnalyticsDashboard = () => { | |
| const [selectedPattern, setSelectedPattern] = useState(null); | |
| const chartCanvasRef = useRef(null); | |
| const patterns = [ | |
| { | |
| id: 1, | |
| severity: 'high', | |
| title: 'Падение груза со стеллажей на складе №3', | |
| percentage: 65, | |
| description: '65% случаев падения груза со стеллажей на складе №3 происходит в ночную смену (22:00-06:00).', | |
| hypothesis: 'Недостаточное освещение, усталость персонала.', | |
| reports: 23, | |
| location: 'Склад №3', | |
| timeframe: 'Ночная смена (22:00-06:00)', | |
| recommendations: [ | |
| 'Усилить освещение на складе №3', | |
| 'Ввести дополнительные перерывы в ночную смену', | |
| 'Провести аудит состояния стеллажей' | |
| ] | |
| }, | |
| { | |
| id: 2, | |
| severity: 'medium', | |
| title: 'Случаи падения из-за скользкого пола в Цехе №1', | |
| percentage: 45, | |
| description: 'Слово "скользкий пол" упоминается в 12 отчетах по Цеху №1, чаще всего после проезда уборочной машины.', | |
| hypothesis: 'Неэффективные предупреждающие знаки, неправильное время уборки.', | |
| reports: 12, | |
| location: 'Цех №1', | |
| timeframe: 'После уборки (10:00-11:00)', | |
| recommendations: [ | |
| 'Изменить график уборки на нерабочее время', | |
| 'Установить более заметные предупреждающие знаки', | |
| 'Использовать быстросохнущие чистящие средства' | |
| ] | |
| }, | |
| { | |
| id: 3, | |
| severity: 'medium', | |
| title: 'Микротравмы рук при работе с инструментом', | |
| percentage: 38, | |
| description: '38% случаев микротравм рук происходит при работе с пневмоинструментом в первые 30 минут смены.', | |
| hypothesis: 'Недостаточная разминка, холодные ручки инструмента, спешка в начале смены.', | |
| reports: 15, | |
| location: 'Цех №2, Участок сборки', | |
| timeframe: 'Начало смены (первые 30 мин)', | |
| recommendations: [ | |
| 'Ввести обязательную разминку перед сменой', | |
| 'Установить подогрев ручек пневмоинструмента', | |
| 'Пересмотреть планы работы на начало смены' | |
| ] | |
| }, | |
| { | |
| id: 4, | |
| severity: 'low', | |
| title: 'Ложные срабатывания пожарной сигнализации', | |
| percentage: 85, | |
| description: '85% ложных срабатываний пожарной сигнализации происходит в зоне сварки из-за повышенного задымления.', | |
| hypothesis: 'Неправильное размещение датчиков, недостаточная вентиляция.', | |
| reports: 28, | |
| location: 'Цех №2, Зона сварки', | |
| timeframe: 'Дневная смена', | |
| recommendations: [ | |
| 'Переместить датчики дальше от зоны сварки', | |
| 'Улучшить вентиляцию в зоне сварки', | |
| 'Установить датчики другого типа' | |
| ] | |
| } | |
| ]; | |
| const getSeverityConfig = (severity) => { | |
| switch(severity) { | |
| case 'high': | |
| return { | |
| color: 'from-red-500 to-red-700', | |
| bgColor: 'bg-red-50', | |
| borderColor: 'border-red-400', | |
| textColor: 'text-red-700', | |
| badgeColor: 'bg-red-100 text-red-700 border-red-300', | |
| label: 'ВЫСОКИЙ РИСК' | |
| }; | |
| case 'medium': | |
| return { | |
| color: 'from-yellow-500 to-orange-600', | |
| bgColor: 'bg-yellow-50', | |
| borderColor: 'border-yellow-400', | |
| textColor: 'text-yellow-700', | |
| badgeColor: 'bg-yellow-100 text-yellow-700 border-yellow-300', | |
| label: 'СРЕДНИЙ РИСК' | |
| }; | |
| default: | |
| return { | |
| color: 'from-blue-500 to-blue-700', | |
| bgColor: 'bg-blue-50', | |
| borderColor: 'border-blue-400', | |
| textColor: 'text-blue-700', | |
| badgeColor: 'bg-blue-100 text-blue-700 border-blue-300', | |
| label: 'НИЗКИЙ РИСК' | |
| }; | |
| } | |
| }; | |
| // Draw incident chart | |
| useEffect(() => { | |
| const canvas = chartCanvasRef.current; | |
| if (!canvas) return; | |
| const ctx = canvas.getContext('2d'); | |
| // Background | |
| ctx.fillStyle = '#ffffff'; | |
| ctx.fillRect(0, 0, canvas.width, canvas.height); | |
| // Data for 2022-2023 | |
| const months = ['Янв', 'Фев', 'Мар', 'Апр', 'Май', 'Июн', 'Июл', 'Авг', 'Сен', 'Окт', 'Ноя', 'Дек']; | |
| const incidents2022 = [12, 15, 10, 14, 18, 16, 13, 11, 9, 8, 7, 10]; | |
| const incidents2023 = [8, 7, 6, 5, 7, 6, 5, 4, 5, 4, 3, 4]; | |
| const maxIncidents = 20; | |
| const barWidth = 25; | |
| const spacing = 60; | |
| const startX = 60; | |
| const startY = 40; | |
| const chartHeight = canvas.height - 80; | |
| // Grid | |
| ctx.strokeStyle = '#e5e7eb'; | |
| ctx.lineWidth = 1; | |
| for (let i = 0; i <= 4; i++) { | |
| const y = startY + (chartHeight / 4) * i; | |
| ctx.beginPath(); | |
| ctx.moveTo(startX, y); | |
| ctx.lineTo(canvas.width - 40, y); | |
| ctx.stroke(); | |
| // Y-axis labels | |
| ctx.fillStyle = '#6b7280'; | |
| ctx.font = '12px Arial'; | |
| ctx.textAlign = 'right'; | |
| ctx.fillText((maxIncidents - (i * 5)).toString(), startX - 10, y + 4); | |
| } | |
| // Bars | |
| months.forEach((month, index) => { | |
| const x = startX + index * spacing; | |
| // 2022 bar | |
| const height2022 = (incidents2022[index] / maxIncidents) * chartHeight; | |
| ctx.fillStyle = '#ef4444'; | |
| ctx.fillRect(x, startY + chartHeight - height2022, barWidth, height2022); | |
| // 2023 bar | |
| const height2023 = (incidents2023[index] / maxIncidents) * chartHeight; | |
| ctx.fillStyle = '#10b981'; | |
| ctx.fillRect(x + barWidth + 5, startY + chartHeight - height2023, barWidth, height2023); | |
| // Month label | |
| ctx.fillStyle = '#374151'; | |
| ctx.font = '11px Arial'; | |
| ctx.textAlign = 'center'; | |
| ctx.fillText(month, x + barWidth + 2, canvas.height - 20); | |
| }); | |
| // Legend | |
| ctx.fillStyle = '#ef4444'; | |
| ctx.fillRect(startX, 10, 20, 15); | |
| ctx.fillStyle = '#374151'; | |
| ctx.font = 'bold 12px Arial'; | |
| ctx.textAlign = 'left'; | |
| ctx.fillText('2022', startX + 25, 22); | |
| ctx.fillStyle = '#10b981'; | |
| ctx.fillRect(startX + 80, 10, 20, 15); | |
| ctx.fillStyle = '#374151'; | |
| ctx.fillText('2023', startX + 105, 22); | |
| // Title | |
| ctx.fillStyle = '#1f2937'; | |
| ctx.font = 'bold 14px Arial'; | |
| ctx.textAlign = 'center'; | |
| ctx.fillText('Динамика инцидентов по месяцам', canvas.width / 2, canvas.height - 5); | |
| }, []); | |
| return ( | |
| <div className="min-h-screen bg-gradient-to-br from-slate-50 via-white to-red-50 p-6"> | |
| <div className="max-w-7xl mx-auto"> | |
| {/* Header */} | |
| <div className="bg-gradient-to-r from-red-600 to-orange-600 rounded-xl shadow-2xl p-6 mb-6 border-2 border-red-300"> | |
| <div className="flex justify-between items-center"> | |
| <div className="flex items-center gap-4"> | |
| <div className="bg-white p-3 rounded-xl shadow-lg"> | |
| <Brain size={32} className="text-red-600" /> | |
| </div> | |
| <div> | |
| <h1 className="text-3xl font-bold text-white">Аналитика инцидентов</h1> | |
| <p className="text-red-50 text-sm font-medium flex items-center gap-2"> | |
| <Calendar size={16} /> | |
| Период: 2022-2023 • Mindia AI Analysis | |
| </p> | |
| </div> | |
| </div> | |
| <div className="text-right"> | |
| <p className="text-red-100 text-sm">Всего проанализировано</p> | |
| <p className="text-4xl font-bold text-white">487</p> | |
| <p className="text-red-100 text-xs">отчетов об инцидентах</p> | |
| </div> | |
| </div> | |
| </div> | |
| <div className="grid grid-cols-3 gap-6"> | |
| {/* Left Column - Patterns */} | |
| <div className="col-span-2 space-y-6"> | |
| {/* Patterns List */} | |
| <div className="bg-white rounded-xl shadow-xl p-6 border-2 border-gray-200"> | |
| <div className="flex items-center gap-2 mb-6"> | |
| <Target className="text-red-600" size={24} /> | |
| <h2 className="text-2xl font-bold text-gray-800">Обнаруженные закономерности</h2> | |
| </div> | |
| <div className="space-y-4"> | |
| {patterns.map((pattern, index) => { | |
| const config = getSeverityConfig(pattern.severity); | |
| return ( | |
| <div | |
| key={pattern.id} | |
| className={`rounded-xl p-6 border-2 ${config.borderColor} ${config.bgColor} cursor-pointer hover:shadow-lg transition-all fade-in-up stagger-${index + 1}`} | |
| onClick={() => setSelectedPattern(pattern)} | |
| > | |
| <div className="flex items-start gap-4 mb-4"> | |
| <div className="flex-shrink-0"> | |
| <span className={`inline-block px-3 py-1 rounded-full text-xs font-bold border-2 ${config.badgeColor}`}> | |
| {config.label} | |
| </span> | |
| </div> | |
| <div className="flex-1"> | |
| <h3 className="text-lg font-bold text-gray-800 mb-2"> | |
| {index + 1}. {pattern.title} | |
| </h3> | |
| <p className="text-gray-700 mb-3"> | |
| {pattern.description} | |
| </p> | |
| <div className="flex items-center gap-4 mb-3"> | |
| <div className="flex items-center gap-2 text-sm text-gray-600"> | |
| <FileText size={16} /> | |
| <span className="font-semibold">{pattern.reports} отчетов</span> | |
| </div> | |
| <div className={`px-3 py-1 rounded-full text-xs font-bold ${config.textColor} bg-white border`}> | |
| {pattern.percentage}% случаев | |
| </div> | |
| </div> | |
| <div className="bg-white rounded-lg p-3 border border-gray-200"> | |
| <div className="flex items-start gap-2"> | |
| <Lightbulb className="text-yellow-500 flex-shrink-0 mt-0.5" size={18} /> | |
| <div> | |
| <p className="text-xs text-gray-600 font-semibold mb-1">Гипотеза:</p> | |
| <p className="text-sm text-gray-800">{pattern.hypothesis}</p> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| <button className="w-full bg-blue-600 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded-lg flex items-center justify-center gap-2 transition-colors"> | |
| <FileText size={18} /> | |
| Посмотреть отчеты | |
| </button> | |
| </div> | |
| ); | |
| })} | |
| </div> | |
| </div> | |
| {/* Chart */} | |
| <div className="bg-white rounded-xl shadow-xl p-6 border-2 border-gray-200"> | |
| <div className="flex items-center gap-2 mb-4"> | |
| <BarChart className="text-blue-600" size={24} /> | |
| <h2 className="text-xl font-bold text-gray-800">Динамика инцидентов</h2> | |
| </div> | |
| <canvas | |
| ref={chartCanvasRef} | |
| width={800} | |
| height={300} | |
| className="w-full" | |
| /> | |
| </div> | |
| </div> | |
| {/* Right Column - Details and Stats */} | |
| <div className="col-span-1 space-y-6"> | |
| {/* Selected Pattern Details */} | |
| {selectedPattern ? ( | |
| <div className="bg-white rounded-xl shadow-xl p-6 border-2 border-purple-200 fade-in-up"> | |
| <div className="flex items-center gap-2 mb-4"> | |
| <Target className="text-purple-600" size={24} /> | |
| <h2 className="text-lg font-bold text-gray-800">Детали анализа</h2> | |
| </div> | |
| <div className="space-y-4"> | |
| <div> | |
| <p className="text-xs text-gray-600 font-semibold mb-1">Локация:</p> | |
| <p className="text-sm font-bold text-gray-800">{selectedPattern.location}</p> | |
| </div> | |
| <div> | |
| <p className="text-xs text-gray-600 font-semibold mb-1">Временной период:</p> | |
| <p className="text-sm font-bold text-gray-800">{selectedPattern.timeframe}</p> | |
| </div> | |
| <div> | |
| <p className="text-xs text-gray-600 font-semibold mb-1">Количество случаев:</p> | |
| <p className="text-2xl font-bold text-red-600">{selectedPattern.reports}</p> | |
| </div> | |
| <div> | |
| <p className="text-xs text-gray-600 font-semibold mb-2">Рекомендации AI:</p> | |
| <div className="space-y-2"> | |
| {selectedPattern.recommendations.map((rec, index) => ( | |
| <div key={index} className="flex items-start gap-2 bg-green-50 p-2 rounded border border-green-200"> | |
| <CheckCircle className="text-green-600 flex-shrink-0 mt-0.5" size={16} /> | |
| <p className="text-xs text-gray-800">{rec}</p> | |
| </div> | |
| ))} | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| ) : ( | |
| <div className="bg-white rounded-xl shadow-xl p-6 border-2 border-gray-200 text-center"> | |
| <Target size={48} className="mx-auto mb-3 text-gray-300" /> | |
| <p className="text-sm text-gray-600"> | |
| Нажмите на закономерность для просмотра деталей | |
| </p> | |
| </div> | |
| )} | |
| {/* Statistics */} | |
| <div className="bg-white rounded-xl shadow-xl p-6 border-2 border-blue-200"> | |
| <h2 className="text-lg font-bold text-gray-800 mb-4">Эффективность</h2> | |
| <div className="space-y-4"> | |
| <div className="bg-gradient-to-br from-green-500 to-green-600 rounded-lg p-4 text-white"> | |
| <div className="flex items-center gap-2 mb-1"> | |
| <TrendingDown size={18} /> | |
| <p className="text-sm font-semibold">Снижение инцидентов</p> | |
| </div> | |
| <p className="text-3xl font-bold">-40%</p> | |
| <p className="text-xs opacity-90">за 2023 год</p> | |
| </div> | |
| <div className="bg-gradient-to-br from-blue-500 to-blue-600 rounded-lg p-4 text-white"> | |
| <div className="flex items-center gap-2 mb-1"> | |
| <Brain size={18} /> | |
| <p className="text-sm font-semibold">Найдено закономерностей</p> | |
| </div> | |
| <p className="text-3xl font-bold">{patterns.length}</p> | |
| <p className="text-xs opacity-90">системных причин</p> | |
| </div> | |
| <div className="bg-gradient-to-br from-purple-500 to-purple-600 rounded-lg p-4 text-white"> | |
| <div className="flex items-center gap-2 mb-1"> | |
| <AlertTriangle size={18} /> | |
| <p className="text-sm font-semibold">Предотвращено повторов</p> | |
| </div> | |
| <p className="text-3xl font-bold">67</p> | |
| <p className="text-xs opacity-90">потенциальных инцидентов</p> | |
| </div> | |
| </div> | |
| </div> | |
| {/* AI Insights */} | |
| <div className="bg-gradient-to-br from-indigo-600 to-purple-700 rounded-xl shadow-xl p-6 text-white"> | |
| <div className="flex items-center gap-2 mb-4"> | |
| <Brain size={24} /> | |
| <h2 className="text-lg font-bold">AI Инсайты</h2> | |
| </div> | |
| <div className="space-y-3 text-sm"> | |
| <p className="opacity-90"> | |
| Анализ выявил, что <span className="font-bold">большинство инцидентов</span> имеют системные, а не случайные причины. | |
| </p> | |
| <p className="opacity-90"> | |
| <span className="font-bold">Время суток</span> является ключевым фактором в 73% случаев. | |
| </p> | |
| <p className="opacity-90"> | |
| Рекомендуется сфокусироваться на <span className="font-bold">изменении условий труда</span>, а не на дисциплинарных мерах. | |
| </p> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| ); | |
| }; | |
| const root = ReactDOM.createRoot(document.getElementById('root')); | |
| root.render(<IncidentAnalyticsDashboard />); | |
| </script> | |
| </body> | |
| </html> | |