bad1 / index.html
Karmashek's picture
Add 3 files
d1d360c verified
<!DOCTYPE html>
<html lang="ru">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
<title>MES System</title>
<script src="https://cdn.tailwindcss.com "></script>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css "/>
<style>
.notification-badge {
position: absolute;
top: 0;
right: 0;
font-size: 0.625rem;
line-height: 1.25rem;
width: 1.25rem;
height: 1.25rem;
display: flex;
align-items: center;
justify-content: center;
border-radius: 9999px;
}
.has-event:hover {
background-color: #BFDBFE !important;
}
</style>
</head>
<body class="bg-gray-50 text-white font-sans">
<div id="app" class="flex h-screen overflow-hidden bg-gray-50"></div>
<script type="module">
import { createRoot } from 'https://unpkg.com/react @18/umd/react.development.js';
import ReactDOM from 'https://unpkg.com/react-dom @18/umd/react-dom.development.js';
const root = ReactDOM.createRoot(document.getElementById('app'));
// Mock data
const equipmentData = [
{
id: 1,
name: "Токарный станок CNC-2000",
type: "Станок",
serial: "CNC2000-001",
inventory: "INV-1001",
status: "active",
lastMaintenance: "2023-05-15",
nextMaintenance: "2023-06-15",
maintenanceInterval: 30,
description: "Токарный станок с ЧПУ"
},
{
id: 2,
name: "Конвейерная линия A-12",
type: "Конвейер",
serial: "CONV-A12-045",
inventory: "INV-1042",
status: "maintenance",
lastMaintenance: "2023-05-20",
nextMaintenance: "2023-06-20",
maintenanceInterval: 30,
description: "Линия для сборки продукции"
},
{
id: 3,
name: "Воздушный компрессор V-50",
type: "Компрессор",
serial: "COMP-V50-112",
inventory: "INV-1078",
status: "repair",
lastMaintenance: "2023-04-10",
nextMaintenance: "2023-06-10",
maintenanceInterval: 60,
description: "Промышленный воздушный компрессор"
}
];
const users = [
{id: 1, name: "Иван Петров", role: "admin", email: "ivan@example.com"},
{id: 2, name: "Петр Смирнов", role: "engineer", email: "petr@example.com"},
{id: 3, name: "Анна Иванова", role: "viewer", email: "anna@example.com"}
];
const maintenanceEvents = [
{equipmentId: 1, date: "2023-06-15", status: "planned", responsible: "Иванов А.П."},
{equipmentId: 2, date: "2023-06-20", status: "planned", responsible: "Петров И.С."},
{equipmentId: 3, date: "2023-06-10", status: "overdue", responsible: "Сидоров В.М."}
];
function formatDate(dateStr) {
if (!dateStr) return "";
const d = new Date(dateStr);
return `${d.getDate()}.${d.getMonth()+1}.${d.getFullYear()}`;
}
function calculateNextMaintenanceDate(commissionDate, intervalDays) {
if (!commissionDate) return '';
const nextDate = new Date(new Date(commissionDate).getTime() + parseInt(intervalDays) * 24 * 60 * 60 * 1000);
return nextDate.toISOString().split('T')[0];
}
function Dashboard({ setActiveTab }) {
const activeCount = equipmentData.length;
const completedCount = equipmentData.filter(e => e.lastMaintenance).length;
const overdueCount = equipmentData.filter(e => new Date(e.nextMaintenance) < new Date()).length;
const repairCount = equipmentData.filter(e => e.status === "repair").length;
return (
<div class="p-6">
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-6 mb-6">
<div class="bg-white rounded-lg shadow p-6 text-gray-800">
<div class="flex items-center">
<i class="fas fa-tools text-xl text-blue-500 mr-4"></i>
<div>
<p class="text-sm text-gray-500">Всего оборудования</p>
<h3 class="text-2xl font-bold">{activeCount}</h3>
</div>
</div>
</div>
<div class="bg-white rounded-lg shadow p-6 text-gray-800">
<div class="flex items-center">
<i class="fas fa-check-circle text-xl text-green-500 mr-4"></i>
<div>
<p class="text-sm text-gray-500">Выполнено ТО</p>
<h3 class="text-2xl font-bold">{completedCount}</h3>
</div>
</div>
</div>
<div class="bg-white rounded-lg shadow p-6 text-gray-800">
<div class="flex items-center">
<i class="fas fa-exclamation-triangle text-xl text-yellow-500 mr-4"></i>
<div>
<p class="text-sm text-gray-500">Просрочено ТО</p>
<h3 class="text-2xl font-bold">{overdueCount}</h3>
</div>
</div>
</div>
<div class="bg-white rounded-lg shadow p-6 text-gray-800">
<div class="flex items-center">
<i class="fas fa-wrench text-xl text-red-500 mr-4"></i>
<div>
<p class="text-sm text-gray-500">Текущие ремонты</p>
<h3 class="text-2xl font-bold">{repairCount}</h3>
</div>
</div>
</div>
</div>
<div class="bg-white rounded-lg shadow overflow-hidden">
<div class="p-4 border-b flex justify-between items-center">
<h2 class="text-lg font-semibold">Оборудование</h2>
<button onClick={() => setActiveTab("equipment")} class="bg-blue-600 hover:bg-blue-700 text-white px-4 py-2 rounded-lg flex items-center">
<i class="fas fa-plus mr-2"></i> Перейти к оборудованию
</button>
</div>
<div class="overflow-x-auto">
<table class="min-w-full divide-y divide-gray-200">
<thead class="bg-gray-50">
<tr>
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">ID</th>
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Название</th>
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Тип</th>
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Статус</th>
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Последнее ТО</th>
</tr>
</thead>
<tbody class="bg-white divide-y divide-gray-200">
{equipmentData.map(eq => (
<tr key={eq.id}>
<td class="px-6 py-4 whitespace-nowrap text-sm font-medium text-gray-900">{eq.id}</td>
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500">{eq.name}</td>
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500">{eq.type}</td>
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500">
<span class={`px-2 py-1 rounded-full text-xs font-semibold ${
eq.status === "active" ? "bg-green-100 text-green-800" :
eq.status === "maintenance" ? "bg-yellow-100 text-yellow-800" : "bg-red-100 text-red-800"
}`}>
{eq.status === "active" ? "Активно" : eq.status === "maintenance" ? "ТО" : "Ремонт"}
</span>
</td>
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500">{formatDate(eq.lastMaintenance)}</td>
</tr>
))}
</tbody>
</table>
</div>
</div>
</div>
);
}
function Equipment({ setEquipmentList, setActiveTab }) {
const [showModal, setShowModal] = React.useState(false);
const [newEq, setNewEq] = React.useState({
name: "",
type: "",
serial: "",
inventory: "",
commissionDate: "",
maintenanceInterval: 30,
description: ""
});
const handleAdd = () => {
if (!newEq.name || !newEq.type || !newEq.serial || !newEq.inventory || !newEq.commissionDate) {
alert("Заполните все обязательные поля");
return;
}
const id = equipmentData.length > 0 ? Math.max(...equipmentData.map(e => e.id)) + 1 : 1;
equipmentData.push({
...newEq,
id,
status: "active",
nextMaintenance: calculateNextMaintenanceDate(newEq.commissionDate, newEq.maintenanceInterval)
});
setEquipmentList([...equipmentData]);
setShowModal(false);
alert("Оборудование добавлено");
};
return (
<div class="p-6">
<h2 class="text-2xl font-bold mb-6">Оборудование</h2>
<div class="bg-white rounded-lg shadow overflow-hidden">
<div class="p-4 border-b flex justify-between items-center">
<h3 class="text-lg font-semibold">Список оборудования</h3>
<button onClick={() => setShowModal(true)} class="bg-blue-600 hover:bg-blue-700 text-white px-4 py-2 rounded-lg flex items-center">
<i class="fas fa-plus mr-2"></i> Добавить
</button>
</div>
<div class="overflow-x-auto">
<table class="min-w-full divide-y divide-gray-200">
<thead class="bg-gray-50">
<tr>
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase">ID</th>
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase">Название</th>
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase">Тип</th>
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase">Статус</th>
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase">Последнее ТО</th>
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase">Действия</th>
</tr>
</thead>
<tbody class="bg-white divide-y divide-gray-200">
{equipmentData.map(eq => (
<tr key={eq.id}>
<td class="px-6 py-4 whitespace-nowrap text-sm font-medium text-gray-900">{eq.id}</td>
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500">{eq.name}</td>
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500">{eq.type}</td>
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500">
<span class={`px-2 py-1 rounded-full text-xs font-semibold ${
eq.status === "active" ? "bg-green-100 text-green-800" :
eq.status === "maintenance" ? "bg-yellow-100 text-yellow-800" : "bg-red-100 text-red-800"
}`}>
{eq.status === "active" ? "Активно" : eq.status === "maintenance" ? "ТО" : "Ремонт"}
</span>
</td>
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500">{formatDate(eq.lastMaintenance)}</td>
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500">
<button class="text-blue-600 hover:text-blue-900 mr-3"><i class="fas fa-edit"></i></button>
<button class="text-red-600 hover:text-red-900"><i class="fas fa-trash"></i></button>
</td>
</tr>
))}
</tbody>
</table>
</div>
</div>
{showModal && (
<div class="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50">
<div class="bg-white rounded-lg w-full max-w-md p-6">
<div class="flex justify-between items-center mb-4">
<h3 class="text-lg font-semibold">Добавить оборудование</h3>
<button onClick={() => setShowModal(false)} class="text-gray-500">&times;</button>
</div>
<form class="space-y-4">
<input value={newEq.name} onInput={(e) => setNewEq({...newEq, name: e.target.value})} placeholder="Название" required class="w-full px-4 py-2 border rounded-lg"/>
<select value={newEq.type} onChange={(e) => setNewEq({...newEq, type: e.target.value})} required class="w-full px-4 py-2 border rounded-lg">
<option value="">Выберите тип</option>
<option>Станок</option>
<option>Конвейер</option>
<option>Компрессор</option>
<option>Генератор</option>
<option>Насос</option>
</select>
<input value={newEq.serial} onInput={(e) => setNewEq({...newEq, serial: e.target.value})} placeholder="Серийный номер" required class="w-full px-4 py-2 border rounded-lg"/>
<input value={newEq.inventory} onInput={(e) => setNewEq({...newEq, inventory: e.target.value})} placeholder="Инвентарный номер" required class="w-full px-4 py-2 border rounded-lg"/>
<input value={newEq.commissionDate} type="date" onInput={(e) => setNewEq({...newEq, commissionDate: e.target.value})} required class="w-full px-4 py-2 border rounded-lg"/>
<input value={newEq.maintenanceInterval} type="number" min="1" onInput={(e) => setNewEq({...newEq, maintenanceInterval: e.target.value})} placeholder="Интервал ТО (дни)" required class="w-full px-4 py-2 border rounded-lg"/>
<textarea value={newEq.description} onInput={(e) => setNewEq({...newEq, description: e.target.value})} rows="3" placeholder="Описание" class="w-full px-4 py-2 border rounded-lg"></textarea>
</form>
<div class="mt-4 flex justify-end space-x-2">
<button onClick={() => setShowModal(false)} class="px-4 py-2 border rounded-lg hover:bg-gray-100">Отмена</button>
<button onClick={handleAdd} class="px-4 py-2 bg-blue-600 text-white rounded-lg hover:bg-blue-700">Сохранить</button>
</div>
</div>
</div>
)}
</div>
);
}
function Schedule() {
return (
<div class="p-6">
<h2 class="text-2xl font-bold mb-6">График ТО</h2>
<div class="grid grid-cols-1 lg:grid-cols-3 gap-6">
<div class="lg:col-span-2 bg-white rounded-lg shadow p-4">
<h3 class="text-lg font-semibold mb-4">Календарь ТО</h3>
<div class="flex justify-between items-center mb-4">
<h4 class="font-medium">Июнь 2023</h4>
<div class="flex space-x-2">
<button class="p-2 rounded-full hover:bg-gray-100"><i class="fas fa-chevron-left"></i></button>
<button class="p-2 rounded-full hover:bg-gray-100"><i class="fas fa-chevron-right"></i></button>
</div>
</div>
<div class="grid grid-cols-7 gap-1 mb-2">
{['Пн', 'Вт', 'Ср', 'Чт', 'Пт', 'Сб', 'Вс'].map(day => (
<div class="text-center text-xs font-medium text-gray-500">{day}</div>
))}
</div>
<div class="grid grid-cols-7 gap-1">
{[...Array(35)].map((_, i) => {
const day = i - 4;
const hasEvent = maintenanceEvents.some(event => new Date(event.date).getDate() === day && new Date(event.date).getMonth() === 5);
return (
<div class={`h-10 flex items-center justify-center border rounded ${hasEvent ? 'bg-blue-50' : ''}>{day > 0 && day <= 30 ? day : ''}</div>
);
})}
</div>
</div>
</div>
</div>
);
}
function Users() {
return (
<div class="p-6">
<h2 class="text-2xl font-bold mb-6">Пользователи</h2>
<div class="bg-white rounded-lg shadow overflow-hidden">
<div class="p-4 border-b">
<h3 class="text-lg font-semibold">Список пользователей</h3>
</div>
<div class="overflow-x-auto">
<table class="min-w-full divide-y divide-gray-200">
<thead class="bg-gray-50">
<tr>
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase">Имя</th>
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase">Email</th>
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase">Роль</th>
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase">Действия</th>
</tr>
</thead>
<tbody class="bg-white divide-y divide-gray-200">
{users.map(user => (
<tr key={user.id}>
<td class="px-6 py-4 whitespace-nowrap text-sm font-medium text-gray-900">{user.name}</td>
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500">{user.email}</td>
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500">
<span class={`px-2 py-1 rounded-full text-xs font-semibold ${
user.role === "admin" ? "bg-purple-100 text-purple-800" :
user.role === "engineer" ? "bg-blue-100 text-blue-800" : "bg-gray-100 text-gray-800"
}`}>
{user.role === "admin" ? "Администратор" : user.role === "engineer" ? "Инженер" : "Просмотр"}
</span>
</td>
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500">
<button class="text-blue-600 hover:text-blue-900 mr-3"><i class="fas fa-edit"></i></button>
<button class="text-red-600 hover:text-red-900"><i class="fas fa-trash"></i></button>
</td>
</tr>
))}
</tbody>
</table>
</div>
</div>
</div>
);
}
function App() {
const [activeTab, setActiveTab] = React.useState("dashboard");
const [isSidebarCollapsed, setIsSidebarCollapsed] = React.useState(false);
const renderContent = () => {
switch (activeTab) {
case "dashboard": return <Dashboard setActiveTab={setActiveTab}/>;
case "schedule": return <Schedule />;
case "equipment": return <Equipment setEquipmentList={setActiveTab} setActiveTab={setActiveTab}/>;
case "users": return <Users />;
default: return <Dashboard setActiveTab={setActiveTab}/>;
}
};
return (
<div class="flex h-screen overflow-hidden bg-gray-50">
{/* Sidebar */}
<div class={`sidebar bg-blue-800 text-white transition-all duration-300 ${isSidebarCollapsed ? 'collapsed w-16' : 'w-64'} flex flex-col`}>
<div class="p-4 flex items-center justify-between border-b border-blue-700">
<div class="flex items-center">
<i class="fas fa-cogs text-2xl mr-3"></i>
{!isSidebarCollapsed && <span class="text-xl font-bold">MES System</span>}
</div>
<button onClick={() => setIsSidebarCollapsed(!isSidebarCollapsed)} class="hover:text-blue-200">
<i class="fas fa-bars"></i>
</button>
</div>
<nav class="p-4 flex-1 overflow-y-auto">
<div class="mb-6">
<p class="uppercase text-xs font-semibold text-blue-300 mb-2">Основное</p>
<button onClick={() => setActiveTab("dashboard")} class={`flex items-center py-2 px-3 rounded-lg w-full text-left ${activeTab === "dashboard" ? "bg-blue-700" : "hover:bg-blue-700"} mb-2`}>
<i class="fas fa-tachometer-alt mr-3"></i>
{!isSidebarCollapsed && <span>Главная</span>}
</button>
<button onClick={() => setActiveTab("schedule")} class={`flex items-center py-2 px-3 rounded-lg w-full text-left ${activeTab === "schedule" ? "bg-blue-700" : "hover:bg-blue-700"} mb-2`}>
<i class="fas fa-calendar-alt mr-3"></i>
{!isSidebarCollapsed && <span>График ТО</span>}
</button>
<button onClick={() => setActiveTab("equipment")} class={`flex items-center py-2 px-3 rounded-lg w-full text-left ${activeTab === "equipment" ? "bg-blue-700" : "hover:bg-blue-700"} mb-2`}>
<i class="fas fa-tools mr-3"></i>
{!isSidebarCollapsed && <span>Оборудование</span>}
</button>
</div>
<div class="mb-6">
<p class="uppercase text-xs font-semibold text-blue-300 mb-2">Настройки</p>
<button onClick={() => setActiveTab("users")} class={`flex items-center py-2 px-3 rounded-lg w-full text-left ${activeTab === "users" ? "bg-blue-700" : "hover:bg-blue-700"} mb-2`}>
<i class="fas fa-users-cog mr-3"></i>
{!isSidebarCollapsed && <span>Пользователи</span>}
</button>
</div>
</nav>
<div class="p-4 border-t border-blue-700 flex items-center">
<img src="https://placehold.co/40x40?text=User " alt="User" class="w-10 h-10 rounded-full mr-3"/>
{!isSidebarCollapsed && (
<div>
<p class="font-medium">Иван Петров</p>
<p class="text-xs text-blue-300">Администратор</p>
</div>
)}
</div>
</div>
{/* Main Content */}
<div class="main-content flex-1 overflow-auto">
<header class="bg-white shadow-sm py-4 px-6 flex justify-between items-center">
<h1 class="text-2xl font-bold text-gray-800">
{activeTab === "dashboard" && "Главная панель"}
{activeTab === "schedule" && "График ТО"}
{activeTab === "equipment" && "Оборудование"}
{activeTab === "users" && "Пользователи"}
</h1>
<div class="flex items-center space-x-4">
<button class="p-2 rounded-full hover:bg-gray-100 relative">
<i class="fas fa-bell text-gray-600"></i>
<span class="notification-badge bg-red-500 text-white text-xs rounded-full h-5 w-5 flex items-center justify-center absolute top-0 right-0">5</span>
</button>
<div class="relative">
<input type="text" placeholder="Поиск..." class="pl-10 pr-4 py-2 border rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500"/>
<i class="fas fa-search absolute left-3 top-3 text-gray-400"></i>
</div>
</div>
</header>
<div class="p-6">
{renderContent()}
</div>
</div>
</div>
);
}
root.render(<App />);
</script>
<p style="border-radius: 8px; text-align: center; font-size: 12px; color: #fff; margin-top: 16px;position: fixed; left: 8px; bottom: 8px; z-index: 10; background: rgba(0, 0, 0, 0.8); padding: 4px 8px;">Made with <img src="https://enzostvs-deepsite.hf.space/logo.svg" alt="DeepSite Logo" style="width: 16px; height: 16px; vertical-align: middle;display:inline-block;margin-right:3px;filter:brightness(0) invert(1);"><a href="https://enzostvs-deepsite.hf.space" style="color: #fff;text-decoration: underline;" target="_blank" >DeepSite</a> - 🧬 <a href="https://enzostvs-deepsite.hf.space?remix=Karmashek/bad1" style="color: #fff;text-decoration: underline;" target="_blank" >Remix</a></p></body>
</html>