581 lines
20 KiB
JavaScript
581 lines
20 KiB
JavaScript
let currentTab = 'biot';
|
||
let currentSubtab = null;
|
||
let events = { biot: [], ctpsr_main: [], gloves: [] };
|
||
let currentFilter = 'все';
|
||
let currentUser = null;
|
||
|
||
// API_URL пустой — используем относительные пути
|
||
const API_URL = '';
|
||
|
||
// Проверка авторизации при загрузке
|
||
async function checkAuth() {
|
||
const token = localStorage.getItem('token');
|
||
const name = localStorage.getItem('userName');
|
||
const id = localStorage.getItem('userId');
|
||
if (token && name && id) {
|
||
currentUser = { token, name, id };
|
||
showApp();
|
||
await loadEvents();
|
||
render();
|
||
} else {
|
||
showLogin();
|
||
}
|
||
}
|
||
|
||
function showLogin() {
|
||
document.getElementById('login-screen').classList.add('on');
|
||
document.getElementById('app-content').classList.remove('on');
|
||
}
|
||
|
||
function showApp() {
|
||
document.getElementById('login-screen').classList.remove('on');
|
||
document.getElementById('app-content').classList.add('on');
|
||
document.getElementById('user-name').textContent = currentUser.name;
|
||
// Показываем кнопку админки только для admin
|
||
if (currentUser.id === 'admin' || currentUser.name === 'Администратор') {
|
||
document.getElementById('admin-btn').style.display = 'inline-block';
|
||
}
|
||
}
|
||
|
||
async function login(username, password) {
|
||
try {
|
||
const res = await fetch(API_URL + '/service/login?username=' + encodeURIComponent(username) + '&password=' + encodeURIComponent(password), {
|
||
method: 'GET'
|
||
});
|
||
const data = await res.json();
|
||
if (res.ok) {
|
||
currentUser = { token: data.token, name: data.name, id: username };
|
||
localStorage.setItem('token', data.token);
|
||
localStorage.setItem('userName', data.name);
|
||
localStorage.setItem('userId', username);
|
||
showApp();
|
||
await loadEvents();
|
||
render();
|
||
} else {
|
||
alert(data.error || 'Ошибка входа');
|
||
}
|
||
} catch (e) {
|
||
alert('Ошибка соединения: ' + e.message);
|
||
}
|
||
}
|
||
} catch (e) {
|
||
alert('Ошибка соединения: ' + e.message);
|
||
}
|
||
}
|
||
|
||
function logout() {
|
||
localStorage.removeItem('token');
|
||
localStorage.removeItem('userName');
|
||
localStorage.removeItem('userId');
|
||
currentUser = null;
|
||
showLogin();
|
||
}
|
||
|
||
document.getElementById('login-form').addEventListener('submit', function(e) {
|
||
e.preventDefault();
|
||
const username = document.getElementById('username').value;
|
||
const password = document.getElementById('password').value;
|
||
login(username, password);
|
||
});
|
||
|
||
checkAuth();
|
||
|
||
// Админ панель
|
||
async function openAdmin() {
|
||
if (!currentUser || currentUser.id !== 'admin') {
|
||
alert('Только для администратора');
|
||
return;
|
||
}
|
||
document.getElementById('admin-modal').classList.add('on');
|
||
await loadUsers();
|
||
}
|
||
|
||
function closeAdmin() {
|
||
document.getElementById('admin-modal').classList.remove('on');
|
||
}
|
||
|
||
async function loadUsers() {
|
||
try {
|
||
const res = await fetch(API_URL + '/service/users', {
|
||
headers: { 'Authorization': 'Bearer ' + currentUser.token }
|
||
});
|
||
if (!res.ok) {
|
||
throw new Error('Ошибка загрузки: ' + res.status);
|
||
}
|
||
const users = await res.json();
|
||
const list = document.getElementById('users-list');
|
||
list.innerHTML = users.map(u => `
|
||
<div style="background: #252830; padding: 12px; border-radius: 8px; margin-bottom: 10px; display: flex; justify-content: space-between; align-items: center;">
|
||
<div>
|
||
<strong style="color: #f4f4f5;">${u.name}</strong>
|
||
<div style="color: #8a8a92; font-size: 13px;">ID: ${u.id}</div>
|
||
</div>
|
||
${u.id !== 'admin' ? `
|
||
<button class="btn btn-secondary" onclick="deleteUser('${u.id}')" style="padding: 6px 12px; font-size: 13px;">Удалить</button>
|
||
` : '<span style="color: #8a8a92; font-size: 13px;">Администратор</span>'}
|
||
</div>
|
||
`).join('');
|
||
document.getElementById('admin-stats').innerHTML = 'Всего пользователей: ' + users.length;
|
||
} catch (e) {
|
||
console.error('Error loading users:', e);
|
||
document.getElementById('users-list').innerHTML = '<p style="color: #f87171;">Ошибка: ' + e.message + '</p>';
|
||
}
|
||
}
|
||
|
||
document.getElementById('add-user-form').addEventListener('submit', async function(e) {
|
||
e.preventDefault();
|
||
const id = document.getElementById('new-user-id').value.trim();
|
||
const name = document.getElementById('new-user-name').value.trim();
|
||
const password = document.getElementById('new-user-password').value.trim();
|
||
|
||
try {
|
||
const res = await fetch(API_URL + '/service/users', {
|
||
method: 'POST',
|
||
headers: {
|
||
'Content-Type': 'application/json',
|
||
'Authorization': 'Bearer ' + currentUser.token
|
||
},
|
||
body: JSON.stringify({ id, name, password })
|
||
});
|
||
const data = await res.json();
|
||
if (res.ok) {
|
||
alert('Пользователь добавлен!\n\nID: ' + data.user.id + '\nФИО: ' + data.user.name + '\nПароль: ' + data.user.password + '\n\nЗапишите данные!');
|
||
document.getElementById('add-user-form').reset();
|
||
await loadUsers();
|
||
} else {
|
||
alert('Ошибка: ' + data.error);
|
||
}
|
||
} catch (e) {
|
||
alert('Ошибка: ' + e.message);
|
||
}
|
||
});
|
||
|
||
async function deleteUser(id) {
|
||
if (!confirm('Удалить пользователя ' + id + '?')) return;
|
||
try {
|
||
const res = await fetch(API_URL + '/service/users/' + id, {
|
||
method: 'DELETE',
|
||
headers: { 'Authorization': 'Bearer ' + currentUser.token }
|
||
});
|
||
const data = await res.json();
|
||
if (res.ok) {
|
||
await loadUsers();
|
||
alert('Пользователь удалён');
|
||
} else {
|
||
alert('Ошибка: ' + data.error);
|
||
}
|
||
} catch (e) {
|
||
alert('Ошибка: ' + e.message);
|
||
}
|
||
}
|
||
|
||
async function loadEvents() {
|
||
if (!currentUser) return;
|
||
try {
|
||
const headers = { 'Authorization': 'Bearer ' + currentUser.token };
|
||
const [biotRes, ctpsrRes, glovesRes] = await Promise.all([
|
||
fetch(API_URL + '/service/events?tab=biot', { headers }),
|
||
fetch(API_URL + '/service/events?tab=ctpsr_main', { headers }),
|
||
fetch(API_URL + '/service/events?tab=gloves', { headers })
|
||
]);
|
||
events.biot = await biotRes.json();
|
||
events.ctpsr_main = await ctpsrRes.json();
|
||
events.gloves = await glovesRes.json();
|
||
console.log('Loaded events:', { biot: events.biot.length, ctpsr_main: events.ctpsr_main.length, gloves: events.gloves.length });
|
||
} catch (e) {
|
||
console.error('Ошибка загрузки:', e);
|
||
}
|
||
}
|
||
|
||
function switchTab(tab) {
|
||
currentTab = tab;
|
||
document.querySelectorAll('.tab').forEach(function(t) { t.classList.remove('active'); });
|
||
var activeBtn = document.querySelector('.tab[onclick="switchTab(\'' + tab + '\')"]');
|
||
if (activeBtn) activeBtn.classList.add('active');
|
||
|
||
var subtabsContainer = document.getElementById('subtabs');
|
||
if (tab === 'ctpsr') {
|
||
subtabsContainer.style.display = 'flex';
|
||
currentSubtab = 'ctpsr_main';
|
||
document.getElementById('page-title').textContent = 'Информация по ЦТПСР';
|
||
} else {
|
||
subtabsContainer.style.display = 'none';
|
||
currentSubtab = null;
|
||
document.getElementById('page-title').textContent = tab === 'biot' ? 'Информация по БиОТ' : 'Мероприятия';
|
||
}
|
||
console.log('switchTab:', tab, 'currentSubtab:', currentSubtab);
|
||
render();
|
||
}
|
||
|
||
function switchSubtab(subtab) {
|
||
currentSubtab = subtab;
|
||
document.querySelectorAll('.subtab').forEach(function(t) { t.classList.remove('active'); });
|
||
var activeBtn = document.querySelector('.subtab[onclick="switchSubtab(\'' + subtab + '\')"]');
|
||
if (activeBtn) activeBtn.classList.add('active');
|
||
console.log('switchSubtab:', subtab);
|
||
render();
|
||
}
|
||
|
||
function escapeHtml(text) {
|
||
if (!text) return '';
|
||
var div = document.createElement('div');
|
||
div.textContent = text;
|
||
return div.innerHTML;
|
||
}
|
||
|
||
function formatDate(dateStr) {
|
||
if (!dateStr) return '';
|
||
var d = new Date(dateStr);
|
||
return d.toLocaleDateString('ru-RU', { day: 'numeric', month: 'long', year: 'numeric' });
|
||
}
|
||
|
||
function setFilter(status) {
|
||
currentFilter = status;
|
||
render();
|
||
}
|
||
|
||
function render() {
|
||
var search = document.getElementById('search').value.toLowerCase();
|
||
var key = currentSubtab || currentTab;
|
||
var currentEvents = events[key] || [];
|
||
var isGloves = key === 'gloves';
|
||
|
||
console.log('render key:', key, 'events count:', currentEvents.length);
|
||
|
||
var filtered;
|
||
if (isGloves) {
|
||
filtered = currentEvents.filter(function(e) {
|
||
return (e.quantity && e.quantity.toLowerCase().includes(search)) ||
|
||
(e.location && e.location.toLowerCase().includes(search)) ||
|
||
(e.responsible && e.responsible.toLowerCase().includes(search));
|
||
});
|
||
} else {
|
||
filtered = currentEvents.filter(function(e) {
|
||
var matchSearch = e.title.toLowerCase().includes(search) ||
|
||
(e.description && e.description.toLowerCase().includes(search)) ||
|
||
(e.organizer && e.organizer.toLowerCase().includes(search));
|
||
var matchFilter = currentFilter === 'все' || e.status === currentFilter;
|
||
return matchSearch && matchFilter;
|
||
});
|
||
}
|
||
|
||
var statsEl = document.getElementById('stats');
|
||
var chipsEl = document.getElementById('chips');
|
||
|
||
if (!isGloves) {
|
||
var statuses = ['запланировано', 'в процессе', 'завершено', 'отменено'];
|
||
var counts = { все: currentEvents.length };
|
||
statuses.forEach(function(s) { counts[s] = currentEvents.filter(function(ev) { return ev.status === s; }).length; });
|
||
|
||
statsEl.innerHTML = `
|
||
<div class="stat-card">
|
||
<div class="number">${counts.все}</div>
|
||
<div class="label">Всего</div>
|
||
</div>
|
||
<div class="stat-card">
|
||
<div class="number">${counts['запланировано']}</div>
|
||
<div class="label">Запланировано</div>
|
||
</div>
|
||
<div class="stat-card">
|
||
<div class="number">${counts['в процессе']}</div>
|
||
<div class="label">В процессе</div>
|
||
</div>
|
||
<div class="stat-card">
|
||
<div class="number">${counts['завершено']}</div>
|
||
<div class="label">Завершено</div>
|
||
</div>
|
||
`;
|
||
|
||
chipsEl.innerHTML = ['все', ...statuses].map(function(s) {
|
||
return `<div class="chip ${currentFilter === s ? 'active' : ''}" onclick="setFilter('${s}')">
|
||
${s.charAt(0).toUpperCase() + s.slice(1)} (${counts[s] || 0})
|
||
</div>`;
|
||
}).join('');
|
||
} else {
|
||
statsEl.innerHTML = '';
|
||
chipsEl.innerHTML = '';
|
||
}
|
||
|
||
var container = document.getElementById('events');
|
||
if (filtered.length === 0) {
|
||
container.innerHTML = `
|
||
<div class="empty-state">
|
||
<h3>Нет записей</h3>
|
||
<p>Нажмите «Добавить», чтобы создать первую запись</p>
|
||
</div>
|
||
`;
|
||
return;
|
||
}
|
||
|
||
if (isGloves) {
|
||
container.innerHTML = `
|
||
<div style="overflow-x: auto;">
|
||
<table class="gloves-table">
|
||
<thead>
|
||
<tr>
|
||
<th>Количество</th>
|
||
<th>Место эксплуатации</th>
|
||
<th>Ответственное лицо</th>
|
||
<th>Последнее испытание</th>
|
||
<th>Следующее испытание</th>
|
||
<th>Срок (мес)</th>
|
||
<th>Действия</th>
|
||
</tr>
|
||
</thead>
|
||
<tbody>
|
||
${filtered.map(function(e) {
|
||
return `<tr>
|
||
<td>${escapeHtml(e.quantity || '')}</td>
|
||
<td>${escapeHtml(e.location || '')}</td>
|
||
<td>${escapeHtml(e.responsible || '')}</td>
|
||
<td>${e.lastTestDate ? formatDate(e.lastTestDate) : ''}</td>
|
||
<td>${e.nextTestDate ? formatDate(e.nextTestDate) : ''}</td>
|
||
<td>${e.expiryMonths ? e.expiryMonths + ' мес.' : ''}</td>
|
||
<td>
|
||
<button class="btn btn-edit" onclick="editEvent(${e.id})">Изменить</button>
|
||
<button class="btn btn-delete" onclick="deleteEvent(${e.id})">Удалить</button>
|
||
</td>
|
||
</tr>`;
|
||
}).join('')}
|
||
</tbody>
|
||
</table>
|
||
</div>
|
||
`;
|
||
} else {
|
||
container.innerHTML = filtered.map(function(e) {
|
||
return `<div class="event-card">
|
||
<div class="event-header">
|
||
<div class="event-title">${escapeHtml(e.title)}</div>
|
||
<span class="event-status status-${e.status.replace(/ /g, '_')}">${e.status}</span>
|
||
</div>
|
||
<div class="event-meta">
|
||
${e.date ? `<span>📅 ${formatDate(e.date)}</span>` : ''}
|
||
${e.time ? `<span>⏰ ${e.time}</span>` : ''}
|
||
${e.location ? `<span>📍 ${escapeHtml(e.location)}</span>` : ''}
|
||
${e.organizer ? `<span>👤 ${escapeHtml(e.organizer)}</span>` : ''}
|
||
</div>
|
||
${e.description ? `<div class="event-description">${escapeHtml(e.description)}</div>` : ''}
|
||
<div class="event-actions">
|
||
<button class="btn btn-edit" onclick="editEvent(${e.id})">Изменить</button>
|
||
<button class="btn btn-delete" onclick="deleteEvent(${e.id})">Удалить</button>
|
||
</div>
|
||
</div>`;
|
||
}).join('');
|
||
}
|
||
}
|
||
|
||
function openModal(editId = null) {
|
||
var modal = document.getElementById('modal');
|
||
var form = document.getElementById('form');
|
||
var title = document.getElementById('modal-title');
|
||
|
||
console.log('=== OPEN MODAL ===');
|
||
console.log('currentTab:', currentTab, 'currentSubtab:', currentSubtab);
|
||
|
||
form.reset();
|
||
document.getElementById('eventId').value = '';
|
||
|
||
var key = currentSubtab || currentTab;
|
||
var isGloves = key === 'gloves';
|
||
|
||
console.log('key:', key, 'isGloves:', isGloves);
|
||
|
||
var glovesFields = document.getElementById('gloves-fields');
|
||
var eventFields = document.getElementById('event-fields');
|
||
|
||
glovesFields.style.display = isGloves ? 'block' : 'none';
|
||
eventFields.style.display = isGloves ? 'none' : 'block';
|
||
|
||
// Убираем required у скрытых полей
|
||
var glovesInputs = glovesFields.querySelectorAll('input, textarea');
|
||
var eventInputs = eventFields.querySelectorAll('input, textarea, select');
|
||
|
||
glovesInputs.forEach(function(input) {
|
||
input.required = isGloves && input.id !== 'notes';
|
||
});
|
||
|
||
eventInputs.forEach(function(input) {
|
||
input.required = !isGloves;
|
||
});
|
||
|
||
if (editId) {
|
||
var item = events[key].find(function(e) { return e.id === editId; });
|
||
if (item) {
|
||
title.textContent = isGloves ? 'Изменить запись' : 'Изменить мероприятие';
|
||
document.getElementById('eventId').value = item.id;
|
||
|
||
if (isGloves) {
|
||
document.getElementById('quantity').value = item.quantity || '';
|
||
document.getElementById('location').value = item.location || '';
|
||
document.getElementById('responsible').value = item.responsible || '';
|
||
document.getElementById('lastTestDate').value = item.lastTestDate || '';
|
||
document.getElementById('nextTestDate').value = item.nextTestDate || '';
|
||
document.getElementById('expiryMonths').value = item.expiryMonths || '';
|
||
document.getElementById('notes').value = item.notes || '';
|
||
} else {
|
||
document.getElementById('title').value = item.title;
|
||
document.getElementById('date').value = item.date;
|
||
document.getElementById('time').value = item.time || '';
|
||
document.getElementById('eventLocation').value = item.location || '';
|
||
document.getElementById('organizer').value = item.organizer || '';
|
||
document.getElementById('status').value = item.status;
|
||
document.getElementById('description').value = item.description || '';
|
||
}
|
||
}
|
||
} else {
|
||
title.textContent = isGloves ? 'Добавить перчатки' : 'Добавить мероприятие';
|
||
}
|
||
|
||
modal.classList.add('on');
|
||
}
|
||
|
||
function closeModal() {
|
||
document.getElementById('modal').classList.remove('on');
|
||
}
|
||
|
||
function editEvent(id) {
|
||
openModal(id);
|
||
}
|
||
|
||
function addNew() {
|
||
openModal(null);
|
||
}
|
||
|
||
async function deleteEvent(id) {
|
||
if (!confirm('Вы уверены, что хотите удалить эту запись?')) return;
|
||
var key = currentSubtab || currentTab;
|
||
await fetch(API_URL + '/service/events/' + id + '?tab=' + key, {
|
||
method: 'DELETE',
|
||
headers: { 'Authorization': 'Bearer ' + currentUser.token }
|
||
});
|
||
await loadEvents();
|
||
render();
|
||
}
|
||
|
||
document.getElementById('form').addEventListener('submit', async function(e) {
|
||
e.preventDefault();
|
||
|
||
var id = document.getElementById('eventId').value;
|
||
var key = currentSubtab || currentTab;
|
||
var isGloves = key === 'gloves';
|
||
var btn = e.target.querySelector('button[type="submit"]');
|
||
var originalText = btn.textContent;
|
||
|
||
console.log('=== FORM SUBMIT ===');
|
||
console.log('key:', key, 'isGloves:', isGloves, 'currentSubtab:', currentSubtab, 'currentTab:', currentTab);
|
||
|
||
// Временно убираем required у всех полей, чтобы браузер не блокировал
|
||
var allInputs = e.target.querySelectorAll('input, textarea, select');
|
||
var requiredBackup = [];
|
||
allInputs.forEach(function(input, i) {
|
||
requiredBackup[i] = input.required;
|
||
input.required = false;
|
||
});
|
||
|
||
btn.textContent = 'Сохранение...';
|
||
btn.disabled = true;
|
||
|
||
var data = isGloves ? {
|
||
quantity: document.getElementById('quantity').value,
|
||
location: document.getElementById('location').value,
|
||
responsible: document.getElementById('responsible').value,
|
||
lastTestDate: document.getElementById('lastTestDate').value,
|
||
nextTestDate: document.getElementById('nextTestDate').value,
|
||
expiryMonths: document.getElementById('expiryMonths').value,
|
||
notes: document.getElementById('notes').value
|
||
} : {
|
||
title: document.getElementById('title').value,
|
||
date: document.getElementById('date').value,
|
||
time: document.getElementById('time').value,
|
||
location: document.getElementById('eventLocation').value,
|
||
organizer: document.getElementById('organizer').value,
|
||
status: document.getElementById('status').value,
|
||
description: document.getElementById('description').value
|
||
};
|
||
|
||
console.log('Data to save:', data);
|
||
|
||
var url = id ? API_URL + '/service/events/' + id + '?tab=' + key : API_URL + '/service/events?tab=' + key;
|
||
console.log('POST to:', url);
|
||
|
||
try {
|
||
var response = await fetch(url, {
|
||
method: id ? 'PUT' : 'POST',
|
||
headers: {
|
||
'Content-Type': 'application/json',
|
||
'Authorization': 'Bearer ' + currentUser.token
|
||
},
|
||
body: JSON.stringify(data)
|
||
});
|
||
|
||
console.log('Response headers:', response.headers.get('content-type'));
|
||
|
||
var responseText = await response.text();
|
||
console.log('Response status:', response.status, 'body:', responseText);
|
||
|
||
if (!response.ok) {
|
||
throw new Error('Сервер вернул ' + response.status + ': ' + responseText);
|
||
}
|
||
|
||
var result = JSON.parse(responseText);
|
||
console.log('Server response:', result);
|
||
|
||
// Возвращаем required обратно
|
||
allInputs.forEach(function(input, i) {
|
||
input.required = requiredBackup[i];
|
||
});
|
||
|
||
closeModal();
|
||
await loadEvents();
|
||
render();
|
||
alert('✓ Запись сохранена!');
|
||
} catch (error) {
|
||
// Возвращаем required обратно
|
||
allInputs.forEach(function(input, i) {
|
||
input.required = requiredBackup[i];
|
||
});
|
||
|
||
console.error('Error saving:', error);
|
||
alert('Ошибка сохранения: ' + error.message);
|
||
} finally {
|
||
btn.textContent = originalText;
|
||
btn.disabled = false;
|
||
}
|
||
});
|
||
|
||
document.getElementById('modal').addEventListener('click', function(e) {
|
||
if (e.target === e.currentTarget) closeModal();
|
||
});
|
||
|
||
async function testApi() {
|
||
if (!currentUser) {
|
||
alert('Сначала войдите в систему');
|
||
return;
|
||
}
|
||
try {
|
||
const res = await fetch(API_URL + '/service/events?tab=biot', {
|
||
method: 'POST',
|
||
headers: {
|
||
'Content-Type': 'application/json',
|
||
'Authorization': 'Bearer ' + currentUser.token
|
||
},
|
||
body: JSON.stringify({ title: 'Тест API', date: '2026-09-10', status: 'запланировано' })
|
||
});
|
||
const text = await res.text();
|
||
console.log('API Response:', res.status, text);
|
||
if (res.ok) {
|
||
const data = JSON.parse(text);
|
||
alert('API работает! Ответ: ' + JSON.stringify(data));
|
||
await loadEvents();
|
||
render();
|
||
} else {
|
||
alert('API вернул ошибку ' + res.status + ': ' + text);
|
||
}
|
||
} catch (e) {
|
||
console.error('Test failed:', e);
|
||
alert('Ошибка теста API: ' + e.message);
|
||
}
|
||
}
|
||
|
||
loadEvents();
|