баг-фикс 30/34
This commit is contained in:
@@ -5,10 +5,6 @@
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Настройки — Magistr</title>
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap" rel="stylesheet">
|
||||
|
||||
<link rel="stylesheet" href="../css/components.css">
|
||||
<link rel="stylesheet" href="css/main.css">
|
||||
<link rel="stylesheet" href="css/layout.css">
|
||||
|
||||
@@ -1,13 +1,17 @@
|
||||
// Основной модуль страницы настроек
|
||||
import { startDropdownAutoObserver, initAllCustomDropdowns } from '../../js/dropdown.js';
|
||||
import { renderStatusMessage } from '../../js/utils.js';
|
||||
import { SETTINGS_ROLES, capabilitiesForRole } from '../../js/role-capabilities.js';
|
||||
|
||||
import { getToken } from '../../js/api.js';
|
||||
import { initializeSession } from '../../js/api.js';
|
||||
|
||||
// Проверка авторизации
|
||||
const role = localStorage.getItem('role');
|
||||
if (!getToken() || !['ADMIN', 'EDUCATION_OFFICE'].includes(role)) {
|
||||
window.location.href = '/';
|
||||
async function bootstrap() {
|
||||
const session = await initializeSession();
|
||||
if (!session || !SETTINGS_ROLES.includes(session.role)) {
|
||||
window.location.replace('/');
|
||||
return;
|
||||
}
|
||||
const role = session.role;
|
||||
|
||||
// Глобальная инициализация кастомных списков
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
@@ -44,12 +48,9 @@ const ROUTES = {
|
||||
},
|
||||
};
|
||||
|
||||
const ROLE_TABS = {
|
||||
ADMIN: ['general', 'time-slots', 'database', 'edu-forms'],
|
||||
EDUCATION_OFFICE: ['time-slots', 'edu-forms']
|
||||
};
|
||||
const allowedTabs = new Set(ROLE_TABS[role] || []);
|
||||
const defaultTab = role === 'EDUCATION_OFFICE' ? 'time-slots' : 'general';
|
||||
const roleCapabilities = capabilitiesForRole(role);
|
||||
const allowedTabs = new Set(roleCapabilities.settingsTabs);
|
||||
const defaultTab = roleCapabilities.defaultSettingsTab;
|
||||
|
||||
let currentTab = null;
|
||||
|
||||
@@ -129,8 +130,8 @@ async function switchTab(tab) {
|
||||
|
||||
currentTab = tab;
|
||||
} catch (e) {
|
||||
appContent.innerHTML = `<div style="padding:1rem;color:var(--error);">Ошибка загрузки: ${e.message}</div>`;
|
||||
console.error(e);
|
||||
renderStatusMessage(appContent, 'Не удалось загрузить раздел настроек');
|
||||
console.error('Не удалось загрузить раздел настроек');
|
||||
}
|
||||
|
||||
// Закрываем мобильное меню после перехода
|
||||
@@ -140,3 +141,6 @@ async function switchTab(tab) {
|
||||
|
||||
// Вкладка по умолчанию
|
||||
switchTab(defaultTab);
|
||||
}
|
||||
|
||||
await bootstrap();
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { api } from '../../../js/api.js';
|
||||
import { escapeHtml, showAlert, hideAlert } from '../../../js/utils.js';
|
||||
import { escapeHtml, showAlert, hideAlert, renderStatusMessage, renderTableMessage } from '../../../js/utils.js';
|
||||
|
||||
export async function initDatabase() {
|
||||
const tenantsTbody = document.getElementById('tenants-tbody');
|
||||
@@ -12,8 +12,8 @@ export async function initDatabase() {
|
||||
try {
|
||||
const data = await api.get('/api/database/status');
|
||||
const statusBadge = data.connected
|
||||
? '<span class="badge badge-available">Online</span>'
|
||||
: '<span class="badge badge-unavailable">Offline</span>';
|
||||
? '<span class="badge badge-available">Доступно</span>'
|
||||
: '<span class="badge badge-unavailable">Недоступно</span>';
|
||||
|
||||
statusInfo.innerHTML = `
|
||||
<div style="display: flex; align-items: center; gap: 1rem; flex-wrap: wrap;">
|
||||
@@ -36,7 +36,7 @@ export async function initDatabase() {
|
||||
</div>
|
||||
`;
|
||||
} catch (e) {
|
||||
statusInfo.innerHTML = `<div class="form-alert error" style="display:block">Ошибка загрузки статуса: ${e.message}</div>`;
|
||||
renderStatusMessage(statusInfo, 'Не удалось загрузить статус подключения');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -46,7 +46,7 @@ export async function initDatabase() {
|
||||
const tenants = await api.get('/api/database/tenants');
|
||||
renderTenantsTable(tenants);
|
||||
} catch (e) {
|
||||
tenantsTbody.innerHTML = `<tr><td colspan="6" class="loading-row">Ошибка загрузки: ${e.message}</td></tr>`;
|
||||
renderTableMessage(tenantsTbody, 6, 'Не удалось загрузить список тенантов');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -58,8 +58,8 @@ export async function initDatabase() {
|
||||
|
||||
tenantsTbody.innerHTML = tenants.map(t => {
|
||||
const statusBadge = t.connected
|
||||
? '<span class="badge badge-available">Online</span>'
|
||||
: '<span class="badge badge-unavailable">Offline</span>';
|
||||
? '<span class="badge badge-available">Доступно</span>'
|
||||
: '<span class="badge badge-unavailable">Недоступно</span>';
|
||||
|
||||
return `
|
||||
<tr>
|
||||
@@ -96,7 +96,7 @@ export async function initDatabase() {
|
||||
showAlert('add-tenant-alert', `✗ ${result.message}`, 'error');
|
||||
}
|
||||
} catch (e) {
|
||||
showAlert('add-tenant-alert', `Ошибка: ${e.message}`, 'error');
|
||||
showAlert('add-tenant-alert', 'Не удалось проверить подключение', 'error');
|
||||
} finally {
|
||||
btnTest.textContent = 'Тест';
|
||||
btnTest.disabled = false;
|
||||
@@ -122,7 +122,7 @@ export async function initDatabase() {
|
||||
try {
|
||||
const result = await api.post('/api/database/tenants', { name, domain, url, username, password });
|
||||
if (result.success) {
|
||||
showAlert('add-tenant-alert', `Тенант "${escapeHtml(domain)}" добавлен!`, 'success');
|
||||
showAlert('add-tenant-alert', `Тенант «${domain}» добавлен`, 'success');
|
||||
addTenantForm.reset();
|
||||
loadTenants();
|
||||
loadStatus();
|
||||
@@ -130,7 +130,7 @@ export async function initDatabase() {
|
||||
showAlert('add-tenant-alert', result.message, 'error');
|
||||
}
|
||||
} catch (e) {
|
||||
showAlert('add-tenant-alert', `Ошибка: ${e.message}`, 'error');
|
||||
showAlert('add-tenant-alert', 'Не удалось добавить тенант', 'error');
|
||||
}
|
||||
});
|
||||
|
||||
@@ -147,7 +147,7 @@ export async function initDatabase() {
|
||||
loadTenants();
|
||||
loadStatus();
|
||||
} catch (e) {
|
||||
alert(`Ошибка: ${e.message}`);
|
||||
alert('Не удалось удалить тенант');
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@ export async function fetchEducationForms() {
|
||||
allEducationForms = await api.get('/api/education-forms');
|
||||
return allEducationForms;
|
||||
} catch (e) {
|
||||
console.error("Failed to fetch education forms", e);
|
||||
console.error('Не удалось загрузить формы обучения');
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -62,8 +62,8 @@ export async function initTimeSlotsSettings() {
|
||||
form?.addEventListener('submit', saveSlot);
|
||||
resetButton?.addEventListener('click', resetForm);
|
||||
refreshButton?.addEventListener('click', loadAll);
|
||||
startInput?.addEventListener('change', fillDurationIfEmpty);
|
||||
endInput?.addEventListener('change', fillDurationIfEmpty);
|
||||
startInput?.addEventListener('change', calculateDuration);
|
||||
endInput?.addEventListener('change', calculateDuration);
|
||||
tbody?.addEventListener('click', handleSlotTableClick);
|
||||
|
||||
await loadAll();
|
||||
@@ -206,8 +206,7 @@ export async function initTimeSlotsSettings() {
|
||||
orderNumber: Number(orderInput.value),
|
||||
scopeId: activeScopeId,
|
||||
startTime: startInput.value,
|
||||
endTime: endInput.value,
|
||||
durationMinutes: durationInput.value ? Number(durationInput.value) : null
|
||||
endTime: endInput.value
|
||||
};
|
||||
|
||||
try {
|
||||
@@ -272,8 +271,9 @@ export async function initTimeSlotsSettings() {
|
||||
}
|
||||
}
|
||||
|
||||
function fillDurationIfEmpty() {
|
||||
if (durationInput.value || !startInput.value || !endInput.value || startInput.value >= endInput.value) return;
|
||||
function calculateDuration() {
|
||||
durationInput.value = '';
|
||||
if (!startInput.value || !endInput.value || startInput.value >= endInput.value) return;
|
||||
const [startHour, startMinute] = startInput.value.split(':').map(Number);
|
||||
const [endHour, endMinute] = endInput.value.split(':').map(Number);
|
||||
durationInput.value = String((endHour * 60 + endMinute) - (startHour * 60 + startMinute));
|
||||
|
||||
@@ -55,11 +55,11 @@
|
||||
<div class="form-row" style="margin-top: 0.75rem;">
|
||||
<div class="form-group">
|
||||
<label for="tenant-username">Пользователь</label>
|
||||
<input type="text" id="tenant-username" placeholder="postgres" required>
|
||||
<input type="text" id="tenant-username" placeholder="postgres" autocomplete="username" required>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="tenant-password">Пароль</label>
|
||||
<input type="password" id="tenant-password" placeholder="••••••••" required>
|
||||
<input type="password" id="tenant-password" placeholder="••••••••" autocomplete="new-password" required>
|
||||
</div>
|
||||
<button type="button" class="btn btn-md btn-secondary" id="btn-test-connection" style="height: fit-content;">
|
||||
Тест
|
||||
|
||||
@@ -70,7 +70,9 @@
|
||||
|
||||
<label class="form-field" for="time-slot-duration">
|
||||
<span>Минут</span>
|
||||
<input type="number" id="time-slot-duration" min="1" step="1" placeholder="90">
|
||||
<input type="number" id="time-slot-duration" min="1" step="1" readonly
|
||||
aria-describedby="time-slot-duration-hint">
|
||||
<small id="time-slot-duration-hint">Рассчитывается по времени начала и окончания</small>
|
||||
</label>
|
||||
|
||||
<button type="submit" class="btn btn-md btn-primary">Сохранить слот</button>
|
||||
|
||||
Reference in New Issue
Block a user