project-to / index.html
Karmashek's picture
Add 3 files
f7bc4db verified
<!DOCTYPE html>
<html lang="ru">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>График ТО и Главная страница</title>
<script src="https://cdn.tailwindcss.com"></script>
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
<style>
.toast {
position: fixed;
bottom: 20px;
right: 20px;
transform: translateY(100px);
opacity: 0;
transition: all 0.3s ease;
}
.toast.show {
transform: translateY(0);
opacity: 1;
}
.tab-content {
display: none;
}
.tab-content.active {
display: block;
}
.nav-tabs .active {
border-bottom: 3px solid #3b82f6;
color: #3b82f6;
font-weight: 600;
}
</style>
</head>
<body class="bg-gray-50 min-h-screen">
<div class="container mx-auto px-4 py-8">
<!-- Навигационные табы -->
<div class="flex border-b mb-6 nav-tabs">
<button class="px-4 py-2 font-medium active" data-tab="main">Главная</button>
<button class="px-4 py-2 font-medium" data-tab="to">График ТО</button>
</div>
<!-- Главная страница -->
<div id="main" class="tab-content active">
<div class="bg-white rounded-lg shadow-md p-6 mb-6">
<h1 class="text-2xl font-bold text-gray-800 mb-4">Добро пожаловать в систему мониторинга ТО</h1>
<p class="text-gray-600 mb-6">Здесь вы можете отслеживать состояние оборудования и планировать техническое обслуживание.</p>
<div class="grid grid-cols-1 md:grid-cols-3 gap-6 mb-8">
<div class="bg-blue-50 rounded-lg p-4 border border-blue-100">
<div class="flex items-center">
<div class="p-3 rounded-full bg-blue-100 text-blue-600 mr-4">
<i class="fas fa-check-circle text-xl"></i>
</div>
<div>
<h3 class="font-semibold text-gray-800">Выполненные ТО</h3>
<p class="text-2xl font-bold" id="completed-to">0</p>
</div>
</div>
</div>
<div class="bg-yellow-50 rounded-lg p-4 border border-yellow-100">
<div class="flex items-center">
<div class="p-3 rounded-full bg-yellow-100 text-yellow-600 mr-4">
<i class="fas fa-clock text-xl"></i>
</div>
<div>
<h3 class="font-semibold text-gray-800">Запланированные ТО</h3>
<p class="text-2xl font-bold" id="planned-to">0</p>
</div>
</div>
</div>
<div class="bg-red-50 rounded-lg p-4 border border-red-100">
<div class="flex items-center">
<div class="p-3 rounded-full bg-red-100 text-red-600 mr-4">
<i class="fas fa-exclamation-triangle text-xl"></i>
</div>
<div>
<h3 class="font-semibold text-gray-800">Просроченные ТО</h3>
<p class="text-2xl font-bold" id="overdue-to">0</p>
</div>
</div>
</div>
</div>
<div class="bg-white rounded-lg shadow-sm p-4 border border-gray-200">
<h2 class="text-xl font-semibold text-gray-800 mb-4">Статус ТО</h2>
<div class="h-64">
<canvas id="mainChart"></canvas>
</div>
</div>
<div class="mt-6 bg-white rounded-lg shadow-sm p-4 border border-gray-200">
<h2 class="text-xl font-semibold text-gray-800 mb-4">Ближайшие ТО</h2>
<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">Оборудование</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" id="upcoming-to-list">
<!-- Сюда будут добавляться ближайшие ТО -->
</tbody>
</table>
</div>
</div>
</div>
</div>
<!-- Страница графика ТО -->
<div id="to" class="tab-content">
<div class="bg-white rounded-lg shadow-md p-6">
<div class="flex justify-between items-center mb-6">
<h1 class="text-2xl font-bold text-gray-800">График технического обслуживания</h1>
<button id="add-to-btn" 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="grid grid-cols-1 lg:grid-cols-3 gap-6">
<div class="lg:col-span-2">
<div class="bg-white rounded-lg shadow-sm p-4 border border-gray-200 mb-6">
<h2 class="text-xl font-semibold text-gray-800 mb-4">График выполнения ТО</h2>
<div class="h-96">
<canvas id="toChart"></canvas>
</div>
</div>
</div>
<div>
<div class="bg-white rounded-lg shadow-sm p-4 border border-gray-200">
<h2 class="text-xl font-semibold text-gray-800 mb-4">Список ТО</h2>
<div class="space-y-4" id="to-list">
<!-- Сюда будут добавляться элементы ТО -->
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- Модальное окно добавления ТО -->
<div id="to-modal" class="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50 hidden">
<div class="bg-white rounded-lg shadow-xl w-full max-w-md">
<div class="flex justify-between items-center border-b px-6 py-4">
<h3 class="text-lg font-semibold text-gray-800">Добавить техническое обслуживание</h3>
<button id="close-modal" class="text-gray-400 hover:text-gray-600">
<i class="fas fa-times"></i>
</button>
</div>
<div class="p-6">
<form id="to-form">
<div class="mb-4">
<label for="equipment" class="block text-sm font-medium text-gray-700 mb-1">Оборудование</label>
<input type="text" id="equipment" class="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500" required>
</div>
<div class="mb-4">
<label for="to-type" class="block text-sm font-medium text-gray-700 mb-1">Тип ТО</label>
<select id="to-type" class="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500" required>
<option value="">Выберите тип</option>
<option value="Ежедневное">Ежедневное</option>
<option value="Еженедельное">Еженедельное</option>
<option value="Ежемесячное">Ежемесячное</option>
<option value="Квартальное">Квартальное</option>
<option value="Годовое">Годовое</option>
</select>
</div>
<div class="mb-4">
<label for="to-date" class="block text-sm font-medium text-gray-700 mb-1">Дата ТО</label>
<input type="date" id="to-date" class="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500" required>
</div>
<div class="mb-4">
<label for="to-status" class="block text-sm font-medium text-gray-700 mb-1">Статус</label>
<select id="to-status" class="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500" required>
<option value="planned">Запланировано</option>
<option value="completed">Выполнено</option>
<option value="overdue">Просрочено</option>
</select>
</div>
<div class="flex justify-end space-x-3">
<button type="button" id="cancel-to" class="px-4 py-2 border border-gray-300 rounded-md text-gray-700 hover:bg-gray-50">Отмена</button>
<button type="submit" class="px-4 py-2 bg-blue-600 text-white rounded-md hover:bg-blue-700">Сохранить</button>
</div>
</form>
</div>
</div>
</div>
<!-- Уведомление -->
<div id="toast" class="toast bg-green-500 text-white px-6 py-3 rounded-lg shadow-lg">
<div class="flex items-center">
<i class="fas fa-check-circle mr-2"></i>
<span id="toast-message">ТО успешно добавлено!</span>
</div>
</div>
<script>
// Инициализация данных
let toData = JSON.parse(localStorage.getItem('toData')) || [
{ id: 1, equipment: 'Компрессор №1', type: 'Ежемесячное', date: '2023-06-15', status: 'completed' },
{ id: 2, equipment: 'Насосная станция', type: 'Квартальное', date: '2023-07-20', status: 'planned' },
{ id: 3, equipment: 'Вентиляционная система', type: 'Годовое', date: '2023-05-10', status: 'overdue' },
{ id: 4, equipment: 'Трансформатор Т-1', type: 'Еженедельное', date: '2023-06-25', status: 'planned' },
{ id: 5, equipment: 'Генератор Г-2', type: 'Ежедневное', date: '2023-06-10', status: 'completed' }
];
// Сохраняем данные в localStorage
function saveData() {
localStorage.setItem('toData', JSON.stringify(toData));
updateMainPage();
updateTOPage();
}
// Обновление главной страницы
function updateMainPage() {
// Обновляем счетчики
const completed = toData.filter(item => item.status === 'completed').length;
const planned = toData.filter(item => item.status === 'planned').length;
const overdue = toData.filter(item => item.status === 'overdue').length;
document.getElementById('completed-to').textContent = completed;
document.getElementById('planned-to').textContent = planned;
document.getElementById('overdue-to').textContent = overdue;
// Обновляем график на главной
updateMainChart();
// Обновляем список ближайших ТО
updateUpcomingTOList();
}
// Обновление страницы графика ТО
function updateTOPage() {
// Обновляем график ТО
updateTOChart();
// Обновляем список ТО
updateTOList();
}
// Обновление графика на главной
let mainChart;
function updateMainChart() {
const ctx = document.getElementById('mainChart').getContext('2d');
const completed = toData.filter(item => item.status === 'completed').length;
const planned = toData.filter(item => item.status === 'planned').length;
const overdue = toData.filter(item => item.status === 'overdue').length;
if (mainChart) {
mainChart.destroy();
}
mainChart = new Chart(ctx, {
type: 'doughnut',
data: {
labels: ['Выполнено', 'Запланировано', 'Просрочено'],
datasets: [{
data: [completed, planned, overdue],
backgroundColor: [
'#10B981',
'#3B82F6',
'#EF4444'
],
borderWidth: 0
}]
},
options: {
responsive: true,
maintainAspectRatio: false,
plugins: {
legend: {
position: 'bottom'
}
},
cutout: '70%'
}
});
}
// Обновление графика ТО
let toChart;
function updateTOChart() {
const ctx = document.getElementById('toChart').getContext('2d');
// Группируем по месяцам
const months = ['Янв', 'Фев', 'Мар', 'Апр', 'Май', 'Июн', 'Июл', 'Авг', 'Сен', 'Окт', 'Ноя', 'Дек'];
const currentYear = new Date().getFullYear();
// Подготовка данных
const monthlyData = months.map((month, index) => {
const monthNum = index + 1;
const monthStr = monthNum < 10 ? `0${monthNum}` : `${monthNum}`;
const monthStart = `${currentYear}-${monthStr}-01`;
return toData.filter(item => {
const itemDate = new Date(item.date);
return itemDate.getFullYear() === currentYear &&
itemDate.getMonth() === index;
}).length;
});
if (toChart) {
toChart.destroy();
}
toChart = new Chart(ctx, {
type: 'bar',
data: {
labels: months,
datasets: [{
label: 'Количество ТО',
data: monthlyData,
backgroundColor: '#3B82F6',
borderRadius: 4
}]
},
options: {
responsive: true,
maintainAspectRatio: false,
scales: {
y: {
beginAtZero: true,
ticks: {
stepSize: 1
}
}
},
plugins: {
legend: {
display: false
}
}
}
});
}
// Обновление списка ближайших ТО на главной
function updateUpcomingTOList() {
const listElement = document.getElementById('upcoming-to-list');
listElement.innerHTML = '';
// Сортируем по дате и берем 5 ближайших
const sorted = [...toData].sort((a, b) => new Date(a.date) - new Date(b.date));
const upcoming = sorted.slice(0, 5);
upcoming.forEach(item => {
const row = document.createElement('tr');
// Форматируем дату
const dateObj = new Date(item.date);
const formattedDate = dateObj.toLocaleDateString('ru-RU', {
day: '2-digit',
month: '2-digit',
year: 'numeric'
});
// Определяем цвет статуса
let statusClass = '';
let statusText = '';
switch(item.status) {
case 'completed':
statusClass = 'bg-green-100 text-green-800';
statusText = 'Выполнено';
break;
case 'planned':
statusClass = 'bg-blue-100 text-blue-800';
statusText = 'Запланировано';
break;
case 'overdue':
statusClass = 'bg-red-100 text-red-800';
statusText = 'Просрочено';
break;
}
row.innerHTML = `
<td class="px-6 py-4 whitespace-nowrap text-sm font-medium text-gray-900">${item.equipment}</td>
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500">${item.type}</td>
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500">${formattedDate}</td>
<td class="px-6 py-4 whitespace-nowrap">
<span class="px-2 inline-flex text-xs leading-5 font-semibold rounded-full ${statusClass}">
${statusText}
</span>
</td>
`;
listElement.appendChild(row);
});
}
// Обновление списка ТО на странице графика
function updateTOList() {
const listElement = document.getElementById('to-list');
listElement.innerHTML = '';
// Сортируем по дате
const sorted = [...toData].sort((a, b) => new Date(a.date) - new Date(b.date));
sorted.forEach(item => {
const itemElement = document.createElement('div');
itemElement.className = 'bg-gray-50 rounded-lg p-4 border border-gray-200';
// Форматируем дату
const dateObj = new Date(item.date);
const formattedDate = dateObj.toLocaleDateString('ru-RU', {
day: '2-digit',
month: '2-digit',
year: 'numeric'
});
// Определяем цвет статуса
let statusClass = '';
let statusText = '';
switch(item.status) {
case 'completed':
statusClass = 'bg-green-100 text-green-800';
statusText = 'Выполнено';
break;
case 'planned':
statusClass = 'bg-blue-100 text-blue-800';
statusText = 'Запланировано';
break;
case 'overdue':
statusClass = 'bg-red-100 text-red-800';
statusText = 'Просрочено';
break;
}
itemElement.innerHTML = `
<div class="flex justify-between items-start mb-2">
<h3 class="font-medium text-gray-800">${item.equipment}</h3>
<span class="px-2 inline-flex text-xs leading-5 font-semibold rounded-full ${statusClass}">
${statusText}
</span>
</div>
<div class="text-sm text-gray-600 mb-1">Тип: ${item.type}</div>
<div class="text-sm text-gray-600">Дата: ${formattedDate}</div>
<div class="flex justify-end mt-3 space-x-2">
<button class="edit-to text-blue-600 hover:text-blue-800 text-sm" data-id="${item.id}">
<i class="fas fa-edit mr-1"></i> Изменить
</button>
<button class="delete-to text-red-600 hover:text-red-800 text-sm" data-id="${item.id}">
<i class="fas fa-trash-alt mr-1"></i> Удалить
</button>
</div>
`;
listElement.appendChild(itemElement);
});
// Добавляем обработчики для кнопок редактирования и удаления
document.querySelectorAll('.edit-to').forEach(button => {
button.addEventListener('click', function() {
const id = parseInt(this.getAttribute('data-id'));
editTO(id);
});
});
document.querySelectorAll('.delete-to').forEach(button => {
button.addEventListener('click', function() {
const id = parseInt(this.getAttribute('data-id'));
deleteTO(id);
});
});
}
// Редактирование ТО
function editTO(id) {
const item = toData.find(item => item.id === id);
if (!item) return;
document.getElementById('equipment').value = item.equipment;
document.getElementById('to-type').value = item.type;
document.getElementById('to-date').value = item.date;
document.getElementById('to-status').value = item.status;
// Сохраняем ID редактируемого элемента
document.getElementById('to-form').setAttribute('data-edit-id', id);
// Открываем модальное окно
document.getElementById('to-modal').classList.remove('hidden');
}
// Удаление ТО
function deleteTO(id) {
if (confirm('Вы уверены, что хотите удалить это ТО?')) {
toData = toData.filter(item => item.id !== id);
saveData();
showToast('ТО успешно удалено!');
}
}
// Показ уведомления
function showToast(message) {
const toast = document.getElementById('toast');
document.getElementById('toast-message').textContent = message;
toast.classList.add('show');
setTimeout(() => {
toast.classList.remove('show');
}, 3000);
}
// Переключение табов
document.querySelectorAll('[data-tab]').forEach(tab => {
tab.addEventListener('click', function() {
const tabId = this.getAttribute('data-tab');
// Убираем активный класс у всех табов и контента
document.querySelectorAll('[data-tab]').forEach(t => t.classList.remove('active'));
document.querySelectorAll('.tab-content').forEach(c => c.classList.remove('active'));
// Добавляем активный класс текущему табу и контенту
this.classList.add('active');
document.getElementById(tabId).classList.add('active');
});
});
// Обработка формы ТО
document.getElementById('to-form').addEventListener('submit', function(e) {
e.preventDefault();
const equipment = document.getElementById('equipment').value;
const type = document.getElementById('to-type').value;
const date = document.getElementById('to-date').value;
const status = document.getElementById('to-status').value;
const editId = this.getAttribute('data-edit-id');
if (editId) {
// Редактирование существующего ТО
const id = parseInt(editId);
const index = toData.findIndex(item => item.id === id);
if (index !== -1) {
toData[index] = {
id,
equipment,
type,
date,
status
};
showToast('ТО успешно обновлено!');
}
} else {
// Добавление нового ТО
const newId = toData.length > 0 ? Math.max(...toData.map(item => item.id)) + 1 : 1;
toData.push({
id: newId,
equipment,
type,
date,
status
});
showToast('ТО успешно добавлено!');
}
// Сбрасываем форму и закрываем модальное окно
this.reset();
this.removeAttribute('data-edit-id');
document.getElementById('to-modal').classList.add('hidden');
// Сохраняем данные и обновляем интерфейс
saveData();
});
// Открытие модального окна
document.getElementById('add-to-btn').addEventListener('click', function() {
document.getElementById('to-form').reset();
document.getElementById('to-form').removeAttribute('data-edit-id');
document.getElementById('to-modal').classList.remove('hidden');
});
// Закрытие модального окна
document.getElementById('close-modal').addEventListener('click', function() {
document.getElementById('to-modal').classList.add('hidden');
});
document.getElementById('cancel-to').addEventListener('click', function() {
document.getElementById('to-modal').classList.add('hidden');
});
// Инициализация при загрузке
document.addEventListener('DOMContentLoaded', function() {
saveData(); // Это обновит все данные и графики
// Устанавливаем минимальную дату (сегодня) для поля даты
const today = new Date().toISOString().split('T')[0];
document.getElementById('to-date').setAttribute('min', today);
});
</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/project-to" style="color: #fff;text-decoration: underline;" target="_blank" >Remix</a></p></body>
</html>