controlquality / index.html
AssanaliAidarkhan's picture
Upload index.html
2dfb613 verified
<!DOCTYPE html>
<html lang="ru">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Neurobox 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;
}
</style>
</head>
<body>
<div id="root"></div>
<script type="text/babel">
const { useState, useEffect, useRef } = React;
// Lucide Icons as inline SVG components
const Camera = ({ 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="M23 19a2 2 0 0 1-2 2H3a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h4l2-3h6l2 3h4a2 2 0 0 1 2 2z"/>
<circle cx="12" cy="13" r="4"/>
</svg>
);
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 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 Activity = ({ 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="22 12 18 12 15 21 9 3 6 12 2 12"/>
</svg>
);
const Clock = ({ 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"/>
<polyline points="12 6 12 12 16 14"/>
</svg>
);
const TrendingUp = ({ 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 6 13.5 15.5 8.5 10.5 1 18"/>
<polyline points="17 6 23 6 23 12"/>
</svg>
);
const Play = ({ 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}>
<polygon points="5 3 19 12 5 21 5 3"/>
</svg>
);
const Pause = ({ 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="6" y="4" width="4" height="16"/>
<rect x="14" y="4" width="4" height="16"/>
</svg>
);
const QualityControlDashboard = () => {
const [currentTime, setCurrentTime] = useState(new Date());
const [inspectedCount, setInspectedCount] = useState(4512);
const [defectsFound, setDefectsFound] = useState(28);
const [isRunning, setIsRunning] = useState(true);
const [events, setEvents] = useState([
{ id: 77812, time: '10:32:04', type: 'defect', defectType: 'Царапина', severity: 'high' },
{ id: 77811, time: '10:31:55', type: 'ok', defectType: null, severity: null },
{ id: 77810, time: '10:31:48', type: 'defect', defectType: 'Непрокрас', severity: 'medium' },
{ id: 77809, time: '10:31:41', type: 'ok', defectType: null, severity: null },
{ id: 77808, time: '10:31:34', type: 'defect', defectType: 'Скол', severity: 'high' },
]);
const [currentItem, setCurrentItem] = useState({ id: 77813, status: 'checking' });
const [defectStats, setDefectStats] = useState({
'Царапина': 15,
'Скол': 9,
'Непрокрас': 4
});
const canvasRef = useRef(null);
useEffect(() => {
const timer = setInterval(() => {
setCurrentTime(new Date());
}, 1000);
return () => clearInterval(timer);
}, []);
useEffect(() => {
if (!isRunning) return;
const interval = setInterval(() => {
const newId = currentItem.id + 1;
const isDefective = Math.random() < 0.0062;
setInspectedCount(prev => prev + 1);
if (isDefective) {
setDefectsFound(prev => prev + 1);
const defectTypes = ['Царапина', 'Скол', 'Непрокрас'];
const severities = ['high', 'medium', 'low'];
const defectType = defectTypes[Math.floor(Math.random() * defectTypes.length)];
const severity = severities[Math.floor(Math.random() * severities.length)];
setDefectStats(prev => ({
...prev,
[defectType]: (prev[defectType] || 0) + 1
}));
const newEvent = {
id: newId,
time: new Date().toLocaleTimeString('ru-RU'),
type: 'defect',
defectType,
severity
};
setEvents(prev => [newEvent, ...prev.slice(0, 9)]);
setCurrentItem({ id: newId, status: 'defect', defectType });
} else {
const newEvent = {
id: newId,
time: new Date().toLocaleTimeString('ru-RU'),
type: 'ok',
defectType: null,
severity: null
};
setEvents(prev => [newEvent, ...prev.slice(0, 9)]);
setCurrentItem({ id: newId, status: 'ok' });
}
}, 3000);
return () => clearInterval(interval);
}, [isRunning, currentItem.id]);
useEffect(() => {
const canvas = canvasRef.current;
if (!canvas) return;
const ctx = canvas.getContext('2d');
let animationFrame;
let offset = 0;
const animate = () => {
const gradient = ctx.createLinearGradient(0, 0, 0, canvas.height);
gradient.addColorStop(0, '#ffffff');
gradient.addColorStop(1, '#f0f9ff');
ctx.fillStyle = gradient;
ctx.fillRect(0, 0, canvas.width, canvas.height);
ctx.strokeStyle = 'rgba(59, 130, 246, 0.1)';
ctx.lineWidth = 1;
for (let i = 0; i < 5; i++) {
ctx.beginPath();
ctx.moveTo(0, 50 + i * 60);
ctx.lineTo(canvas.width, 50 + i * 60);
ctx.stroke();
}
const lightGradient = ctx.createRadialGradient(canvas.width / 2, 100, 50, canvas.width / 2, 100, 300);
lightGradient.addColorStop(0, 'rgba(59, 130, 246, 0.15)');
lightGradient.addColorStop(1, 'rgba(59, 130, 246, 0)');
ctx.fillStyle = lightGradient;
ctx.fillRect(0, 0, canvas.width, 300);
const beltGradient = ctx.createLinearGradient(0, canvas.height - 80, 0, canvas.height);
beltGradient.addColorStop(0, '#64748b');
beltGradient.addColorStop(0.5, '#475569');
beltGradient.addColorStop(1, '#64748b');
ctx.fillStyle = beltGradient;
ctx.fillRect(0, canvas.height - 80, canvas.width, 80);
ctx.fillStyle = '#94a3b8';
ctx.fillRect(0, canvas.height - 82, canvas.width, 2);
ctx.fillRect(0, canvas.height, canvas.width, 2);
ctx.strokeStyle = '#cbd5e1';
ctx.lineWidth = 2;
for (let i = -20; i < canvas.width; i += 40) {
ctx.beginPath();
ctx.moveTo(i + offset, canvas.height - 80);
ctx.lineTo(i + offset, canvas.height);
ctx.stroke();
}
for (let x of [50, canvas.width - 50]) {
ctx.fillStyle = '#94a3b8';
ctx.beginPath();
ctx.arc(x, canvas.height - 40, 15, 0, Math.PI * 2);
ctx.fill();
ctx.strokeStyle = '#475569';
ctx.lineWidth = 2;
ctx.stroke();
}
const itemX = canvas.width / 2 - 50;
const itemY = canvas.height - 180;
ctx.fillStyle = 'rgba(0, 0, 0, 0.2)';
ctx.beginPath();
ctx.ellipse(itemX + 50, itemY + 110, 55, 15, 0, 0, Math.PI * 2);
ctx.fill();
const productGradient = ctx.createLinearGradient(itemX, itemY, itemX, itemY + 100);
if (currentItem.status === 'defect') {
productGradient.addColorStop(0, '#fca5a5');
productGradient.addColorStop(0.5, '#f87171');
productGradient.addColorStop(1, '#ef4444');
} else if (currentItem.status === 'ok') {
productGradient.addColorStop(0, '#6ee7b7');
productGradient.addColorStop(0.5, '#34d399');
productGradient.addColorStop(1, '#10b981');
} else {
productGradient.addColorStop(0, '#93c5fd');
productGradient.addColorStop(0.5, '#60a5fa');
productGradient.addColorStop(1, '#3b82f6');
}
ctx.fillStyle = productGradient;
ctx.fillRect(itemX + 35, itemY, 30, 15);
ctx.fillRect(itemX + 20, itemY + 15, 60, 85);
ctx.fillStyle = 'rgba(255, 255, 255, 0.6)';
ctx.fillRect(itemX + 25, itemY + 20, 15, 70);
ctx.fillStyle = currentItem.status === 'defect' ? '#dc2626' : '#2563eb';
ctx.fillRect(itemX + 25, itemY + 40, 50, 30);
ctx.fillStyle = '#ffffff';
ctx.font = 'bold 10px Arial';
ctx.fillText('NEURO', itemX + 32, itemY + 52);
ctx.fillText('PRODUCT', itemX + 28, itemY + 64);
ctx.fillStyle = '#334155';
ctx.fillRect(itemX + 32, itemY - 8, 36, 8);
ctx.fillStyle = '#475569';
ctx.fillRect(itemX + 35, itemY - 5, 30, 5);
ctx.strokeStyle = '#334155';
ctx.lineWidth = 2;
ctx.strokeRect(itemX + 20, itemY + 15, 60, 85);
ctx.strokeRect(itemX + 35, itemY, 30, 15);
if (currentItem.status === 'defect') {
ctx.strokeStyle = '#ef4444';
ctx.lineWidth = 5;
ctx.setLineDash([10, 5]);
ctx.strokeRect(itemX + 10, itemY - 20, 80, 130);
ctx.setLineDash([]);
if (currentItem.defectType === 'Царапина') {
ctx.strokeStyle = '#dc2626';
ctx.lineWidth = 4;
ctx.beginPath();
ctx.moveTo(itemX + 30, itemY + 35);
ctx.lineTo(itemX + 45, itemY + 50);
ctx.stroke();
} else if (currentItem.defectType === 'Скол') {
ctx.fillStyle = '#dc2626';
ctx.beginPath();
ctx.moveTo(itemX + 75, itemY + 60);
ctx.lineTo(itemX + 80, itemY + 55);
ctx.lineTo(itemX + 80, itemY + 65);
ctx.fill();
} else if (currentItem.defectType === 'Непрокрас') {
ctx.fillStyle = 'rgba(220, 38, 38, 0.7)';
ctx.beginPath();
ctx.arc(itemX + 40, itemY + 70, 12, 0, Math.PI * 2);
ctx.fill();
}
ctx.fillStyle = '#ef4444';
ctx.beginPath();
ctx.arc(itemX + 85, itemY - 10, 20, 0, Math.PI * 2);
ctx.fill();
ctx.fillStyle = '#ffffff';
ctx.font = 'bold 24px Arial';
ctx.fillText('!', itemX + 80, itemY - 1);
ctx.fillStyle = '#dc2626';
ctx.font = 'bold 18px Arial';
ctx.fillText('БРАК ОБНАРУЖЕН', itemX - 30, itemY - 35);
} else if (currentItem.status === 'ok') {
ctx.strokeStyle = '#10b981';
ctx.lineWidth = 5;
ctx.beginPath();
ctx.arc(itemX + 50, itemY - 30, 20, 0, Math.PI * 2);
ctx.stroke();
ctx.strokeStyle = '#10b981';
ctx.lineWidth = 5;
ctx.beginPath();
ctx.moveTo(itemX + 40, itemY - 30);
ctx.lineTo(itemX + 47, itemY - 23);
ctx.lineTo(itemX + 62, itemY - 38);
ctx.stroke();
ctx.fillStyle = '#059669';
ctx.font = 'bold 16px Arial';
ctx.fillText('✓ ГОДЕН', itemX + 20, itemY - 35);
}
ctx.fillStyle = 'rgba(51, 65, 85, 0.9)';
ctx.fillRect(itemX + 15, itemY + 105, 70, 20);
ctx.fillStyle = '#FFFFFF';
ctx.font = 'bold 13px monospace';
ctx.fillText(`ID: ${currentItem.id}`, itemX + 20, itemY + 118);
const scanY = (Date.now() / 10) % 200 + itemY - 30;
ctx.strokeStyle = 'rgba(59, 130, 246, 0.8)';
ctx.lineWidth = 3;
ctx.beginPath();
ctx.moveTo(itemX - 20, scanY);
ctx.lineTo(itemX + 120, scanY);
ctx.stroke();
ctx.fillStyle = 'rgba(59, 130, 246, 0.3)';
ctx.fillRect(itemX - 20, scanY - 2, 140, 5);
const drawBackgroundProduct = (x, y, scale, opacity) => {
ctx.globalAlpha = opacity;
ctx.fillStyle = '#94a3b8';
ctx.fillRect(x + 35 * scale, y, 30 * scale, 15 * scale);
ctx.fillRect(x + 20 * scale, y + 15 * scale, 60 * scale, 85 * scale);
ctx.strokeStyle = '#64748b';
ctx.lineWidth = 1;
ctx.strokeRect(x + 20 * scale, y + 15 * scale, 60 * scale, 85 * scale);
ctx.globalAlpha = 1;
};
drawBackgroundProduct(150, itemY + 80, 0.6, 0.6);
drawBackgroundProduct(550, itemY + 90, 0.5, 0.5);
if (isRunning) {
offset = (offset + 2) % 40;
}
animationFrame = requestAnimationFrame(animate);
};
animate();
return () => cancelAnimationFrame(animationFrame);
}, [currentItem, isRunning]);
const defectRate = ((defectsFound / inspectedCount) * 100).toFixed(2);
const getSeverityColor = (severity) => {
switch(severity) {
case 'high': return 'bg-red-100 text-red-700 border-red-400';
case 'medium': return 'bg-yellow-100 text-yellow-700 border-yellow-400';
case 'low': return 'bg-blue-100 text-blue-700 border-blue-400';
default: return 'bg-gray-100 text-gray-700 border-gray-400';
}
};
const getSeverityText = (severity) => {
switch(severity) {
case 'high': return 'Высокая';
case 'medium': return 'Средняя';
case 'low': return 'Низкая';
default: return '';
}
};
return (
<div className="min-h-screen bg-gradient-to-br from-blue-50 via-white to-indigo-50 p-6">
<div className="max-w-7xl mx-auto">
<div className="bg-gradient-to-r from-blue-500 to-indigo-600 rounded-xl shadow-xl p-6 mb-6 border-2 border-blue-300">
<div className="flex justify-between items-center">
<div className="flex items-center gap-4">
<div className="bg-white p-2 rounded-lg">
<Camera size={32} className="text-blue-600" />
</div>
<div>
<h1 className="text-3xl font-bold text-white">Дашборд контроля качества</h1>
<p className="text-blue-50 text-sm font-medium">Линия 3 • Neurobox AI System</p>
</div>
</div>
<div className="flex items-center gap-6">
<button
onClick={() => setIsRunning(!isRunning)}
className="flex items-center gap-2 bg-white text-blue-600 px-5 py-2.5 rounded-lg hover:bg-blue-50 transition-all font-bold shadow-lg hover:shadow-xl"
>
{isRunning ? <Pause size={20} /> : <Play size={20} />}
{isRunning ? 'Пауза' : 'Запуск'}
</button>
<div className="flex items-center gap-2 bg-white px-4 py-2 rounded-lg shadow-md">
<Activity size={20} className={isRunning ? 'text-green-500 animate-pulse' : 'text-gray-400'} />
<span className={`font-bold ${isRunning ? 'text-green-600' : 'text-gray-600'}`}>
{isRunning ? 'Работа' : 'Остановлено'}
</span>
</div>
<div className="flex items-center gap-2 bg-white px-4 py-2 rounded-lg shadow-md">
<Clock size={20} className="text-indigo-600" />
<span className="font-mono text-lg text-gray-800 font-semibold">
{currentTime.toLocaleTimeString('ru-RU')} {currentTime.toLocaleDateString('ru-RU')}
</span>
</div>
</div>
</div>
</div>
<div className="grid grid-cols-3 gap-6">
<div className="col-span-2 space-y-6">
<div className="bg-white rounded-xl shadow-xl p-6 border-2 border-blue-200">
<div className="flex items-center justify-between mb-4">
<h2 className="text-xl font-bold text-gray-800 flex items-center gap-2">
<Camera className="text-blue-500" />
Прямая трансляция с камеры
</h2>
<span className="text-green-600 flex items-center gap-2 text-sm font-semibold bg-green-50 px-3 py-1 rounded-full border border-green-200">
<span className="w-2 h-2 bg-green-500 rounded-full animate-pulse"></span>
В эфире
</span>
</div>
<div className="bg-gradient-to-br from-gray-50 to-blue-50 rounded-lg overflow-hidden border-2 border-gray-300 shadow-inner">
<canvas
ref={canvasRef}
width={800}
height={400}
className="w-full"
/>
</div>
</div>
<div className="bg-white rounded-xl shadow-xl p-6 border-2 border-blue-200">
<h2 className="text-xl font-bold text-gray-800 mb-4 flex items-center gap-2">
<TrendingUp className="text-green-500" />
Статистика за смену
</h2>
<div className="grid grid-cols-3 gap-4">
<div className="bg-gradient-to-br from-blue-500 to-blue-600 rounded-xl p-5 shadow-lg border-2 border-blue-300">
<p className="text-blue-50 text-sm mb-1 font-semibold">Проверено изделий</p>
<p className="text-4xl font-bold text-white">{inspectedCount.toLocaleString('ru-RU')}</p>
</div>
<div className="bg-gradient-to-br from-red-500 to-red-600 rounded-xl p-5 shadow-lg border-2 border-red-300">
<p className="text-red-50 text-sm mb-1 font-semibold">Выявлено брака</p>
<p className="text-4xl font-bold text-white">{defectsFound} <span className="text-xl">({defectRate}%)</span></p>
</div>
<div className="bg-gradient-to-br from-green-500 to-green-600 rounded-xl p-5 shadow-lg border-2 border-green-300">
<p className="text-green-50 text-sm mb-1 font-semibold">Годных изделий</p>
<p className="text-4xl font-bold text-white">{(inspectedCount - defectsFound).toLocaleString('ru-RU')}</p>
</div>
</div>
<div className="mt-6 bg-gradient-to-br from-gray-50 to-blue-50 rounded-xl p-5 border-2 border-gray-200 shadow-inner">
<h3 className="text-gray-800 font-bold mb-3 text-lg">Основные дефекты:</h3>
<div className="space-y-3">
{Object.entries(defectStats).map(([defect, count]) => (
<div key={defect} className="flex items-center justify-between bg-white p-3 rounded-lg shadow-sm border border-gray-200">
<span className="text-gray-700 font-semibold">{defect}</span>
<div className="flex items-center gap-3">
<div className="w-48 bg-gray-200 rounded-full h-3 overflow-hidden border border-gray-300">
<div
className="bg-gradient-to-r from-red-500 to-red-600 h-full rounded-full transition-all duration-500 shadow-sm"
style={{ width: `${(count / defectsFound) * 100}%` }}
></div>
</div>
<span className="text-gray-800 font-bold w-12 text-right text-lg">{count}</span>
</div>
</div>
))}
</div>
</div>
</div>
</div>
<div className="col-span-1">
<div className="bg-white rounded-xl shadow-xl p-6 h-full border-2 border-blue-200">
<h2 className="text-xl font-bold text-gray-800 mb-4 flex items-center gap-2">
<Activity className="text-yellow-500" />
Журнал событий
<span className="text-sm text-gray-500 font-normal ml-auto">Последние 10 мин</span>
</h2>
<div className="space-y-3 overflow-y-auto max-h-[calc(100vh-200px)] pr-2">
{events.map((event, index) => (
<div
key={`${event.id}-${index}`}
className={`rounded-lg p-4 border-2 transition-all duration-300 shadow-md ${
event.type === 'defect'
? 'bg-red-50 border-red-400 hover:shadow-lg'
: 'bg-green-50 border-green-400 hover:shadow-lg'
} ${index === 0 ? 'ring-2 ring-blue-400 shadow-xl' : ''}`}
>
<div className="flex items-start justify-between mb-2">
<span className="text-gray-700 font-mono text-sm font-semibold bg-white px-2 py-1 rounded border border-gray-300">{event.time}</span>
{event.type === 'defect' ? (
<AlertTriangle size={22} className="text-red-600" />
) : (
<CheckCircle size={22} className="text-green-600" />
)}
</div>
<div className="flex items-center justify-between mb-2">
<span className={`font-bold text-lg ${event.type === 'defect' ? 'text-red-700' : 'text-green-700'}`}>
{event.type === 'defect' ? `Дефект: ${event.defectType}` : 'OK'}
</span>
<span className="text-gray-600 text-sm font-semibold bg-white px-2 py-1 rounded border border-gray-300">ID: {event.id}</span>
</div>
{event.type === 'defect' && (
<div className="mt-2">
<span className={`inline-block px-3 py-1 rounded-full text-xs font-bold border-2 ${getSeverityColor(event.severity)}`}>
Срочность: {getSeverityText(event.severity)}
</span>
</div>
)}
{event.type === 'defect' && (
<div className="mt-3 bg-white rounded-lg p-3 h-24 flex items-center justify-center border-2 border-gray-300 shadow-inner">
<div className="w-20 h-20 bg-gradient-to-br from-red-400 to-red-600 rounded-lg border-2 border-red-500 flex items-center justify-center shadow-md">
<span className="text-white text-xs font-bold">ФОТО</span>
</div>
</div>
)}
</div>
))}
</div>
</div>
</div>
</div>
<div className="mt-6 bg-white rounded-xl shadow-xl p-6 border-2 border-blue-200">
<div className="grid grid-cols-3 gap-6 text-center">
<div className="bg-gradient-to-br from-green-50 to-emerald-50 p-5 rounded-xl border-2 border-green-300 shadow-md">
<p className="text-gray-700 text-sm font-semibold mb-1">Снижение брака</p>
<p className="text-4xl font-bold text-green-600">↓ 90%</p>
</div>
<div className="bg-gradient-to-br from-blue-50 to-indigo-50 p-5 rounded-xl border-2 border-blue-300 shadow-md">
<p className="text-gray-700 text-sm font-semibold mb-1">Увеличение скорости линии</p>
<p className="text-4xl font-bold text-blue-600">↑ 15%</p>
</div>
<div className="bg-gradient-to-br from-purple-50 to-pink-50 p-5 rounded-xl border-2 border-purple-300 shadow-md">
<p className="text-gray-700 text-sm font-semibold mb-1">Режим работы</p>
<p className="text-4xl font-bold text-purple-600">24/7</p>
</div>
</div>
</div>
</div>
</div>
);
};
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(<QualityControlDashboard />);
</script>
</body>
</html>