301 lines
14 KiB
JavaScript
Executable File
301 lines
14 KiB
JavaScript
Executable File
import { api } from '../api.js';
|
||
import { escapeHtml, showAlert, hideAlert, initMultiSelect, updateSelectText } from '../utils.js';
|
||
|
||
async function fetchEquipments() {
|
||
try {
|
||
return await api.get('/api/equipments');
|
||
} catch (e) {
|
||
console.error("Failed to fetch equipments", e);
|
||
return [];
|
||
}
|
||
}
|
||
|
||
function renderEquipmentCheckboxes(equipments, containerId, selectTextId, selectedIds = []) {
|
||
const container = document.getElementById(containerId);
|
||
if (!container) return;
|
||
|
||
if (!equipments || !equipments.length) {
|
||
container.innerHTML = '<p class="text-muted"><small>Оборудование отсутствует</small></p>';
|
||
updateSelectText(containerId, selectTextId);
|
||
return;
|
||
}
|
||
|
||
container.innerHTML = equipments.map(eq => `
|
||
<label class="checkbox-container">
|
||
<input type="checkbox" value="${eq.id}" ${selectedIds.includes(eq.id) ? 'checked' : ''}>
|
||
<span>${escapeHtml(eq.name)}</span>
|
||
</label>
|
||
`).join('');
|
||
|
||
updateSelectText(containerId, selectTextId);
|
||
}
|
||
|
||
export async function initClassrooms() {
|
||
const classroomsTbody = document.getElementById('classrooms-tbody');
|
||
const createClassroomForm = document.getElementById('create-classroom-form');
|
||
|
||
const equipmentCheckboxes = document.getElementById('equipment-checkboxes');
|
||
const editEquipmentCheckboxes = document.getElementById('edit-equipment-checkboxes');
|
||
|
||
const modalEditClassroom = document.getElementById('modal-edit-classroom');
|
||
const modalEditClassroomClose = document.getElementById('modal-edit-classroom-close');
|
||
const editClassroomForm = document.getElementById('edit-classroom-form');
|
||
|
||
let allEquipments = [];
|
||
let editingClassroomData = null;
|
||
|
||
initMultiSelect('equipment-select-box', 'equipment-dropdown-menu', 'equipment-select-text', 'equipment-checkboxes');
|
||
initMultiSelect('edit-equipment-select-box', 'edit-equipment-dropdown-menu', 'edit-equipment-select-text', 'edit-equipment-checkboxes');
|
||
|
||
async function loadInitialData() {
|
||
try {
|
||
allEquipments = await fetchEquipments();
|
||
renderEquipmentCheckboxes(allEquipments, 'equipment-checkboxes', 'equipment-select-text');
|
||
await loadClassrooms();
|
||
} catch (e) {
|
||
classroomsTbody.innerHTML = '<tr><td colspan="6" class="loading-row">Ошибка загрузки данных</td></tr>';
|
||
}
|
||
}
|
||
|
||
async function loadClassrooms() {
|
||
try {
|
||
const classrooms = await api.get('/api/classrooms?includeArchived=true');
|
||
renderClassrooms(classrooms);
|
||
} catch (e) {
|
||
classroomsTbody.innerHTML = '<tr><td colspan="6" class="loading-row">Ошибка загрузки</td></tr>';
|
||
}
|
||
}
|
||
|
||
function renderClassrooms(classrooms) {
|
||
if (!classrooms || !classrooms.length) {
|
||
classroomsTbody.innerHTML = '<tr><td colspan="6" class="loading-row">Нет аудиторий</td></tr>';
|
||
return;
|
||
}
|
||
classroomsTbody.innerHTML = classrooms.map(c => {
|
||
const equipHtml = c.equipments && c.equipments.length
|
||
? c.equipments.map(eq => escapeHtml(eq.name)).join(', ')
|
||
: '—';
|
||
|
||
return `
|
||
<tr>
|
||
<td>${c.id}</td>
|
||
<td><strong>${escapeHtml(c.name)}</strong></td>
|
||
<td>${c.capacity} чел.</td>
|
||
<td><small>${equipHtml}</small></td>
|
||
<td>
|
||
<div class="status-cell">
|
||
<span class="badge ${c.isAvailable ? 'badge-available' : 'badge-unavailable'}">
|
||
${c.status === 'ARCHIVED' ? 'Архив' : (c.isAvailable ? 'Доступна' : 'Не доступна')}
|
||
</span>
|
||
<button class="btn-icon-toggle" data-id="${c.id}" data-current-status="${c.isAvailable}" title="Сменить статус">
|
||
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M21 12a9 9 0 01-9 9m9-9a9 9 0 00-9-9m9 9H3m9 9a9 9 0 01-9-9m9 9c1.657 0 3-4.03 3-9s-1.343-9-3-9m0 18c-1.657 0-3-4.03-3-9s1.343-9 3-9m-9 9a9 9 0 019-9"></path></svg>
|
||
</button>
|
||
</div>
|
||
</td>
|
||
<td style="text-align: right;">
|
||
<button class="btn-edit-classroom" data-id="${c.id}">Изменить</button>
|
||
${c.status === 'ARCHIVED'
|
||
? `<button class="btn-restore-classroom" data-id="${c.id}" style="margin-left: 0.5rem;">Восстановить</button>`
|
||
: `<button class="btn-delete" data-id="${c.id}" style="margin-left: 0.5rem;">Архивировать</button>`}
|
||
</td>
|
||
</tr>`;
|
||
}).join('');
|
||
}
|
||
|
||
createClassroomForm.addEventListener('submit', async (e) => {
|
||
e.preventDefault();
|
||
hideAlert('create-classroom-alert');
|
||
const name = document.getElementById('new-classroom-name').value.trim();
|
||
const capacity = parseInt(document.getElementById('new-classroom-capacity').value, 10);
|
||
|
||
const checkedBoxes = Array.from(equipmentCheckboxes.querySelectorAll('input:checked'));
|
||
const equipmentIds = checkedBoxes.map(chk => parseInt(chk.value, 10));
|
||
|
||
if (!name || isNaN(capacity)) { showAlert('create-classroom-alert', 'Заполните обязательные поля', 'error'); return; }
|
||
|
||
try {
|
||
const data = await api.post('/api/classrooms', { name, capacity, equipmentIds, isAvailable: true });
|
||
showAlert('create-classroom-alert', `Аудитория "${escapeHtml(data.name)}" добавлена`, 'success');
|
||
createClassroomForm.reset();
|
||
updateSelectText('equipment-checkboxes', 'equipment-select-text');
|
||
loadClassrooms();
|
||
} catch (e) {
|
||
showAlert('create-classroom-alert', e.message || 'Ошибка создания', 'error');
|
||
}
|
||
});
|
||
|
||
classroomsTbody.addEventListener('click', async (e) => {
|
||
const btnDelete = e.target.closest('.btn-delete');
|
||
const btnToggleStatus = e.target.closest('.btn-icon-toggle');
|
||
const btnEdit = e.target.closest('.btn-edit-classroom');
|
||
|
||
if (btnDelete) {
|
||
if (!confirm('Вывести аудиторию из эксплуатации? Историческое расписание сохранится.')) return;
|
||
try {
|
||
await api.delete('/api/classrooms/' + btnDelete.dataset.id);
|
||
loadClassrooms();
|
||
} catch (err) { alert('Ошибка архивирования'); }
|
||
}
|
||
|
||
const btnRestore = e.target.closest('.btn-restore-classroom');
|
||
if (btnRestore) {
|
||
try {
|
||
await api.post('/api/classrooms/' + btnRestore.dataset.id + '/restore', {});
|
||
loadClassrooms();
|
||
} catch (err) { alert('Ошибка восстановления аудитории'); }
|
||
}
|
||
|
||
if (btnToggleStatus) {
|
||
const id = btnToggleStatus.dataset.id;
|
||
const currentStatus = btnToggleStatus.dataset.currentStatus === 'true';
|
||
try {
|
||
await api.put('/api/classrooms/' + id, { isAvailable: !currentStatus });
|
||
loadClassrooms();
|
||
} catch (err) { alert('Ошибка изменения статуса'); }
|
||
}
|
||
|
||
if (btnEdit) {
|
||
openEditClassroomModal(btnEdit.dataset.id);
|
||
}
|
||
});
|
||
|
||
async function openEditClassroomModal(id) {
|
||
try {
|
||
// Can optimize by using already loaded classrooms, but fetch is safer to get fresh data
|
||
const classrooms = await api.get('/api/classrooms?includeArchived=true');
|
||
editingClassroomData = classrooms.find(c => c.id == id);
|
||
|
||
if (!editingClassroomData) return;
|
||
|
||
document.getElementById('edit-classroom-id').value = editingClassroomData.id;
|
||
document.getElementById('edit-classroom-name').value = editingClassroomData.name;
|
||
document.getElementById('edit-classroom-capacity').value = editingClassroomData.capacity;
|
||
|
||
const existingEquipIds = editingClassroomData.equipments.map(e => e.id);
|
||
renderEquipmentCheckboxes(allEquipments, 'edit-equipment-checkboxes', 'edit-equipment-select-text', existingEquipIds);
|
||
|
||
hideAlert('edit-classroom-alert');
|
||
modalEditClassroom.classList.add('open');
|
||
} catch (e) {
|
||
alert('Ошибка загрузки данных аудитории');
|
||
}
|
||
}
|
||
|
||
modalEditClassroomClose.addEventListener('click', () => {
|
||
modalEditClassroom.classList.remove('open');
|
||
});
|
||
|
||
modalEditClassroom.addEventListener('click', (e) => {
|
||
if (e.target === modalEditClassroom) {
|
||
modalEditClassroom.classList.remove('open');
|
||
}
|
||
});
|
||
|
||
editClassroomForm.addEventListener('submit', async (e) => {
|
||
e.preventDefault();
|
||
hideAlert('edit-classroom-alert');
|
||
const id = document.getElementById('edit-classroom-id').value;
|
||
const name = document.getElementById('edit-classroom-name').value.trim();
|
||
const capacity = parseInt(document.getElementById('edit-classroom-capacity').value, 10);
|
||
|
||
const checkedBoxes = Array.from(editEquipmentCheckboxes.querySelectorAll('input:checked'));
|
||
const equipmentIds = checkedBoxes.map(chk => parseInt(chk.value, 10));
|
||
|
||
if (!name || isNaN(capacity)) { showAlert('edit-classroom-alert', 'Заполните обязательные поля', 'error'); return; }
|
||
|
||
try {
|
||
const data = await api.put('/api/classrooms/' + id, {
|
||
name,
|
||
capacity,
|
||
equipmentIds,
|
||
isAvailable: editingClassroomData.isAvailable
|
||
});
|
||
modalEditClassroom.classList.remove('open');
|
||
// We show alert on the main create form area or we could use toast
|
||
showAlert('create-classroom-alert', `Аудитория "${escapeHtml(data.name)}" обновлена`, 'success');
|
||
loadClassrooms();
|
||
} catch (e) {
|
||
showAlert('edit-classroom-alert', e.message || 'Ошибка обновления', 'error');
|
||
}
|
||
});
|
||
|
||
// Элементы модалки оборудования
|
||
const btnManageEquipments = document.getElementById('btn-manage-equipments');
|
||
const modalManageEquipments = document.getElementById('modal-manage-equipments');
|
||
const modalManageEquipmentsClose = document.getElementById('modal-manage-equipments-close');
|
||
const manageEquipmentForm = document.getElementById('manage-equipment-form');
|
||
const manageEquipmentNameInput = document.getElementById('manage-equipment-name');
|
||
const manageEquipmentTbody = document.getElementById('manage-equipments-tbody');
|
||
|
||
btnManageEquipments.addEventListener('click', openManageEquipmentsModal);
|
||
modalManageEquipmentsClose.addEventListener('click', () => modalManageEquipments.classList.remove('open'));
|
||
modalManageEquipments.addEventListener('click', (e) => {
|
||
if (e.target === modalManageEquipments) modalManageEquipments.classList.remove('open');
|
||
});
|
||
|
||
async function openManageEquipmentsModal() {
|
||
hideAlert('manage-equipment-alert');
|
||
manageEquipmentForm.reset();
|
||
modalManageEquipments.classList.add('open');
|
||
await loadManageEquipments();
|
||
}
|
||
|
||
async function loadManageEquipments() {
|
||
manageEquipmentTbody.innerHTML = '<tr><td colspan="3" class="loading-row">Загрузка...</td></tr>';
|
||
try {
|
||
allEquipments = await fetchEquipments();
|
||
renderManageEquipmentsTable();
|
||
// Обновим чекбоксы на основной странице
|
||
renderEquipmentCheckboxes(allEquipments, 'equipment-checkboxes', 'equipment-select-text');
|
||
} catch (e) {
|
||
manageEquipmentTbody.innerHTML = '<tr><td colspan="3" class="loading-row">Ошибка загрузки</td></tr>';
|
||
}
|
||
}
|
||
|
||
function renderManageEquipmentsTable() {
|
||
if (!allEquipments || !allEquipments.length) {
|
||
manageEquipmentTbody.innerHTML = '<tr><td colspan="3" class="loading-row">Нет оборудования</td></tr>';
|
||
return;
|
||
}
|
||
manageEquipmentTbody.innerHTML = allEquipments.map(eq => `
|
||
<tr>
|
||
<td>${eq.id}</td>
|
||
<td>${escapeHtml(eq.name)}</td>
|
||
<td><button class="btn-delete btn-delete-equipment" data-id="${eq.id}">Удалить</button></td>
|
||
</tr>
|
||
`).join('');
|
||
}
|
||
|
||
manageEquipmentForm.addEventListener('submit', async (e) => {
|
||
e.preventDefault();
|
||
hideAlert('manage-equipment-alert');
|
||
const name = manageEquipmentNameInput.value.trim();
|
||
if (!name) {
|
||
showAlert('manage-equipment-alert', 'Введите название', 'error');
|
||
return;
|
||
}
|
||
try {
|
||
const data = await api.post('/api/equipments', { name });
|
||
showAlert('manage-equipment-alert', `Оборудование "${escapeHtml(data.name)}" добавлено`, 'success');
|
||
manageEquipmentForm.reset();
|
||
await loadManageEquipments();
|
||
} catch (err) {
|
||
showAlert('manage-equipment-alert', err.message || 'Ошибка добавления', 'error');
|
||
}
|
||
});
|
||
|
||
manageEquipmentTbody.addEventListener('click', async (e) => {
|
||
const btnDelete = e.target.closest('.btn-delete-equipment');
|
||
if (!btnDelete) return;
|
||
if (!confirm('Удалить оборудование из каталога?')) return;
|
||
try {
|
||
await api.delete('/api/equipments/' + btnDelete.dataset.id);
|
||
await loadManageEquipments();
|
||
} catch (err) {
|
||
alert(err.message || 'Ошибка удаления');
|
||
}
|
||
});
|
||
|
||
loadInitialData();
|
||
}
|