Изменил страницу "Кафедра",добавлена модалка с полями для создания записи в Кафедральный файлик

This commit is contained in:
2026-03-31 00:49:37 +03:00
parent 522bc97b8c
commit b14d937062
7 changed files with 544 additions and 52 deletions

View File

@@ -38,14 +38,15 @@ public class AuthController {
!passwordEncoder.matches(request.getPassword(), userOpt.get().getPassword())) { !passwordEncoder.matches(request.getPassword(), userOpt.get().getPassword())) {
return ResponseEntity return ResponseEntity
.status(401) .status(401)
.body(new LoginResponse(false, "Неверное имя пользователя или пароль", null, null, null)); .body(new LoginResponse(false, "Неверное имя пользователя или пароль", null, null, null, null));
} }
User user = userOpt.get(); User user = userOpt.get();
String token = UUID.randomUUID().toString(); String token = UUID.randomUUID().toString();
String roleName = user.getRole().name(); String roleName = user.getRole().name();
String redirect = ROLE_REDIRECTS.getOrDefault(roleName, "/"); String redirect = ROLE_REDIRECTS.getOrDefault(roleName, "/");
Long departmentId = user.getDepartmentId();
return ResponseEntity.ok(new LoginResponse(true, "OK", token, roleName, redirect)); return ResponseEntity.ok(new LoginResponse(true, "OK", token, roleName, redirect, departmentId));
} }
} }

View File

@@ -7,16 +7,18 @@ public class LoginResponse {
private String token; private String token;
private String role; private String role;
private String redirect; private String redirect;
private Long departmentId;
public LoginResponse() { public LoginResponse() {
} }
public LoginResponse(boolean success, String message, String token, String role, String redirect) { public LoginResponse(boolean success, String message, String token, String role, String redirect, Long departmentId) {
this.success = success; this.success = success;
this.message = message; this.message = message;
this.token = token; this.token = token;
this.role = role; this.role = role;
this.redirect = redirect; this.redirect = redirect;
this.departmentId = departmentId;
} }
public boolean isSuccess() { public boolean isSuccess() {
@@ -58,4 +60,12 @@ public class LoginResponse {
public void setRedirect(String redirect) { public void setRedirect(String redirect) {
this.redirect = redirect; this.redirect = redirect;
} }
public Long getDepartmentId() {
return departmentId;
}
public void setDepartmentId(Long departmentId) {
this.departmentId = departmentId;
}
} }

View File

@@ -1,3 +1,82 @@
/* ===== Оверлей для модалок создания записей (к/ф) ===== */
.cs-overlay {
display: none;
position: fixed;
inset: 0;
z-index: 1000;
background: rgba(0, 0, 0, 0.55);
backdrop-filter: blur(4px);
-webkit-backdrop-filter: blur(4px);
}
.cs-overlay.open {
display: block;
}
.cs-overlay-scroll {
width: 100%;
height: 100%;
overflow-y: auto;
padding: 2rem 1rem;
display: flex;
flex-direction: column;
align-items: center;
gap: 1rem;
}
/* Общие стили для обеих модалок */
.cs-modal {
width: 100%;
max-width: 1100px;
position: relative;
animation: csModalAppear 0.25s ease-out;
}
/* Модалка 1 (форма) всегда поверх модалки 2 (таблицы),
чтобы выпадающие списки не уходили под таблицу */
.cs-modal-form {
z-index: 2;
}
.cs-modal-table {
z-index: 1;
}
@keyframes csModalAppear {
from { opacity: 0; transform: translateY(-12px); }
to { opacity: 1; transform: translateY(0); }
}
.cs-modal-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 1rem;
}
.cs-modal-header h2 {
margin: 0;
}
/* Кнопка закрытия */
.btn-close-panel {
background: none;
border: 1px solid var(--bg-card-border);
border-radius: var(--radius-sm);
font-size: 1.3rem;
line-height: 1;
padding: 0.25rem 0.6rem;
color: var(--text-secondary);
cursor: pointer;
transition: color var(--transition), background var(--transition), border-color var(--transition);
}
.btn-close-panel:hover {
color: var(--error);
background: rgba(239, 68, 68, 0.1);
border-color: var(--error);
}
.wrap{ .wrap{
max-width: 900px; max-width: 900px;
margin: 0 auto; margin: 0 auto;

View File

@@ -33,22 +33,14 @@ export async function initDepartment() {
const deptName = departmentSelect.options[departmentSelect.selectedIndex].text; const deptName = departmentSelect.options[departmentSelect.selectedIndex].text;
try { try {
const params = new URLSearchParams({ const params = new URLSearchParams({ departmentId, semesterType, period });
departmentId,
semesterType,
period
});
// Запрос на бэк
const data = await api.get(`/api/department/schedule?${params.toString()}`); const data = await api.get(`/api/department/schedule?${params.toString()}`);
const semesterName = semesterType === 'spring' ? 'весенний' : (semesterType === 'autumn' ? 'осенний' : semesterType); const semesterName = semesterType === 'spring' ? 'весенний' : (semesterType === 'autumn' ? 'осенний' : semesterType);
const periodName = period.replace('-', '/'); // Display 2024-2025 as 2024/2025 const periodName = period.replace('-', '/');
renderScheduleBlock(deptName, semesterName, periodName, data); renderScheduleBlock(deptName, semesterName, periodName, data);
form.reset(); form.reset();
} catch (err) { } catch (err) {
showAlert('schedule-form-alert', err.message || 'Ошибка загрузки данных', 'error'); showAlert('schedule-form-alert', err.message || 'Ошибка загрузки данных', 'error');
} }
@@ -57,8 +49,7 @@ export async function initDepartment() {
function renderScheduleBlock(deptName, semester, period, schedule) { function renderScheduleBlock(deptName, semester, period, schedule) {
const details = document.createElement('details'); const details = document.createElement('details');
details.className = 'table-item'; details.className = 'table-item';
details.open = true; // Сразу открываем новый блок details.open = true;
details.innerHTML = ` details.innerHTML = `
<summary> <summary>
<div class="chev" aria-hidden="true"> <div class="chev" aria-hidden="true">
@@ -75,7 +66,6 @@ export async function initDepartment() {
</div> </div>
<div class="meta">${schedule ? schedule.length : 0} записей</div> <div class="meta">${schedule ? schedule.length : 0} записей</div>
</summary> </summary>
<div class="content"> <div class="content">
<table> <table>
<thead> <thead>
@@ -96,7 +86,6 @@ export async function initDepartment() {
</table> </table>
</div> </div>
`; `;
container.prepend(details); container.prepend(details);
} }
@@ -104,34 +93,306 @@ export async function initDepartment() {
if (!schedule || schedule.length === 0) { if (!schedule || schedule.length === 0) {
return '<tr><td colspan="8" class="loading-row">Нет данных</td></tr>'; return '<tr><td colspan="8" class="loading-row">Нет данных</td></tr>';
} }
return schedule.map(r => ` return schedule.map(r => `
<tr> <tr>
<td>${escapeHtml(r.specialityCode || '-')}</td> <td>${escapeHtml(r.specialityCode || '-')}</td>
<td> <td>${(() => {
${(() => {
const course = r.groupCourse || '-'; const course = r.groupCourse || '-';
const semester = r.semester || '-'; const semester = r.semester || '-';
if (course === '-' && semester === '-') return '-'; if (course === '-' && semester === '-') return '-';
return `${course} | ${semester}`; return `${course} | ${semester}`;
})()} })()}</td>
</td>
<td>${escapeHtml(r.groupName || '-')}</td> <td>${escapeHtml(r.groupName || '-')}</td>
<td>${escapeHtml(r.subjectName || '-')}</td> <td>${escapeHtml(r.subjectName || '-')}</td>
<td>${escapeHtml(r.lessonType || '-')}</td> <td>${escapeHtml(r.lessonType || '-')}</td>
<td>${escapeHtml(r.numberOfHours || '-')}</td> <td>${escapeHtml(r.numberOfHours || '-')}</td>
<td> <td>${r.division === true ? '✓' : ''}</td>
${r.division === true ? '✓' : (r.division === false ? '' : escapeHtml(''))} <td>${(() => {
</td>
<td>
${(() => {
const jobTitle = r.teacherJobTitle || '-'; const jobTitle = r.teacherJobTitle || '-';
const teacherName = r.teacherName || '-'; const teacherName = r.teacherName || '-';
if (jobTitle === '-' && teacherName === '-') return '-'; if (jobTitle === '-' && teacherName === '-') return '-';
return `${jobTitle}, ${teacherName}`; return `${jobTitle}, ${teacherName}`;
})()} })()}</td>
</td>
</tr> </tr>
`).join(''); `).join('');
} }
// =========================================================
// ЛОГИКА ДЛЯ ФУНКЦИОНАЛА "СОЗДАТЬ ЗАПИСЬ (К/Ф)"
// Два модальных окна поверх всего контента в одном оверлее
// =========================================================
const btnCreateSchedule = document.getElementById('btn-create-schedule');
const csOverlay = document.getElementById('cs-overlay');
const modalCreateSchedule = document.getElementById('modal-create-schedule');
const modalCreateScheduleClose = document.getElementById('modal-create-schedule-close');
const formCreateSchedule = document.getElementById('create-schedule-form');
const modalViewSchedules = document.getElementById('modal-view-schedules');
const btnSaveSchedules = document.getElementById('btn-save-schedules');
const preparedSchedulesTbody = document.getElementById('prepared-schedules-tbody');
const csGroupSelect = document.getElementById('cs-group');
const csSubjectSelect = document.getElementById('cs-subject');
const csTeacherSelect = document.getElementById('cs-teacher');
const csDepartmentIdInput = document.getElementById('cs-department-id');
let preparedSchedules = [];
let csGroups = [];
let csSubjects = [];
let csTeachers = [];
const SEMESTER_LABELS = { autumn: 'Осенний', spring: 'Весенний' };
const LESSON_TYPE_LABELS = { 1: 'Лекция', 2: 'Практическая работа', 3: 'Лабораторная работа' };
const localDepartmentId = localStorage.getItem('departmentId');
// ===== Загрузка справочников =====
async function loadDictionariesForSchedule() {
try {
csGroups = await api.get('/api/groups');
csGroupSelect.innerHTML = '<option value="">Выберите группу</option>' +
csGroups.map(g => `<option value="${g.id}">${escapeHtml(g.name)}</option>`).join('');
csSubjects = await api.get('/api/subjects');
csSubjectSelect.innerHTML = '<option value="">Выберите дисциплину</option>' +
csSubjects.map(s => `<option value="${s.id}">${escapeHtml(s.name)}</option>`).join('');
if (localDepartmentId) {
csTeachers = await api.get(`/api/users/teachers/${localDepartmentId}`);
csTeacherSelect.innerHTML = '<option value="">Выберите преподавателя</option>' +
csTeachers.map(t => `<option value="${t.id}">${escapeHtml(t.fullName || t.username)}</option>`).join('');
} else {
csTeacherSelect.innerHTML = '<option value="">Ошибка: Не найден ID кафедры</option>';
}
} catch (e) {
console.error('Ошибка загрузки справочников:', e);
}
}
loadDictionariesForSchedule();
// ===== Открытие / Закрытие оверлея =====
function openOverlay() {
csOverlay.classList.add('open');
document.body.style.overflow = 'hidden'; // Предотвращаем скролл страницы
}
function closeOverlay() {
csOverlay.classList.remove('open');
document.body.style.overflow = '';
hideAlert('create-schedule-alert');
hideAlert('save-schedules-alert');
}
function updateTableVisibility() {
modalViewSchedules.style.display = preparedSchedules.length > 0 ? '' : 'none';
}
// ===== Кнопка «Создать запись» =====
btnCreateSchedule.addEventListener('click', () => {
if (localDepartmentId) {
csDepartmentIdInput.value = localDepartmentId;
} else {
showAlert('schedule-form-alert', 'Требуется перезайти (отсутствует ID кафедры)', 'error');
return;
}
openOverlay();
});
// ===== Закрытие =====
modalCreateScheduleClose.addEventListener('click', closeOverlay);
csOverlay.addEventListener('click', (e) => {
// Закрыть по клику на затемнённый фон (но не по клику на содержимое модалок)
if (e.target === csOverlay || e.target.classList.contains('cs-overlay-scroll')) {
closeOverlay();
}
});
document.addEventListener('keydown', (e) => {
if (e.key === 'Escape' && csOverlay.classList.contains('open')) {
closeOverlay();
}
});
// ===== Рендер таблицы =====
function renderPreparedSchedules() {
if (preparedSchedules.length === 0) {
preparedSchedulesTbody.innerHTML = '<tr><td colspan="10" class="loading-row">Нет записей</td></tr>';
return;
}
preparedSchedulesTbody.innerHTML = preparedSchedules.map((s, index) => {
const groupName = csGroups.find(g => g.id == s.groupId)?.name || s.groupId;
const subjectName = csSubjects.find(sub => sub.id == s.subjectsId)?.name || s.subjectsId;
const teacherName = csTeachers.find(t => t.id == s.teacherId)?.fullName
|| csTeachers.find(t => t.id == s.teacherId)?.username || s.teacherId;
const lessonTypeName = LESSON_TYPE_LABELS[s.lessonTypeId] || 'Неизвестно';
const semLabel = SEMESTER_LABELS[s.semesterType] || s.semesterType;
const periodDisplay = s.period.replace('-', '/');
const divText = s.isDivision ? '✓' : '';
const hasError = !!s._errorMsg;
const rowStyle = hasError ? ' style="background: rgba(239, 68, 68, 0.08);"' : '';
let row = `
<tr${rowStyle}>
<td>${escapeHtml(periodDisplay)}</td>
<td>${escapeHtml(semLabel)}</td>
<td>${s.semester}</td>
<td>${escapeHtml(String(groupName))}</td>
<td>${escapeHtml(String(subjectName))}</td>
<td>${escapeHtml(lessonTypeName)}</td>
<td>${s.numberOfHours}</td>
<td>${divText}</td>
<td>${escapeHtml(String(teacherName))}</td>
<td><button type="button" class="btn-delete" data-index="${index}">Удалить</button></td>
</tr>`;
if (hasError) {
row += `<tr style="background: rgba(239, 68, 68, 0.05);">
<td colspan="10" style="color: var(--error); font-size: 0.85rem; padding: 0.4rem 0.85rem;">
${escapeHtml(s._errorMsg)}
</td>
</tr>`;
}
return row;
}).join('');
}
// ===== Удаление строки из таблицы =====
preparedSchedulesTbody.addEventListener('click', (e) => {
if (e.target.classList.contains('btn-delete')) {
const idx = parseInt(e.target.getAttribute('data-index'), 10);
preparedSchedules.splice(idx, 1);
renderPreparedSchedules();
updateTableVisibility();
}
});
// ===== Очистка полей формы (частичная) =====
// НЕ очищаем select'ы — они остаются заполненными для удобства.
// Пользователь сам изменит нужные поля для следующей записи.
function clearFormFields() {
document.getElementById('cs-hours').value = '';
document.getElementById('cs-division').checked = false;
}
// ===== Добавление записи в список =====
formCreateSchedule.addEventListener('submit', (e) => {
e.preventDefault();
hideAlert('create-schedule-alert');
const depId = csDepartmentIdInput.value;
const period = document.getElementById('cs-period').value;
const semesterType = document.querySelector('input[name="csSemesterType"]:checked')?.value;
const semester = document.getElementById('cs-semester').value;
const groupId = csGroupSelect.value;
const subjectId = csSubjectSelect.value;
const lessonTypeId = document.getElementById('cs-lesson-type').value;
const hours = document.getElementById('cs-hours').value;
const isDivision = document.getElementById('cs-division').checked;
const teacherId = csTeacherSelect.value;
if (!period || !semesterType || !semester || !groupId || !subjectId || !lessonTypeId || !hours || !teacherId) {
showAlert('create-schedule-alert', 'Заполните все обязательные поля', 'error');
return;
}
const newRecord = {
departmentId: Number(depId),
semester: Number(semester),
groupId: Number(groupId),
subjectsId: Number(subjectId),
lessonTypeId: Number(lessonTypeId),
numberOfHours: Number(hours),
isDivision: isDivision,
teacherId: Number(teacherId),
semesterType: semesterType,
period: period
};
// Проверка на дубликат в уже добавленных записях
const isDuplicate = preparedSchedules.some(s =>
s.period === newRecord.period &&
s.semesterType === newRecord.semesterType &&
s.semester === newRecord.semester &&
s.groupId === newRecord.groupId &&
s.subjectsId === newRecord.subjectsId &&
s.lessonTypeId === newRecord.lessonTypeId &&
s.numberOfHours === newRecord.numberOfHours &&
s.isDivision === newRecord.isDivision &&
s.teacherId === newRecord.teacherId
);
if (isDuplicate) {
showAlert('create-schedule-alert', 'Такая запись уже есть в списке', 'error');
return;
}
preparedSchedules.push(newRecord);
clearFormFields();
showAlert('create-schedule-alert', 'Запись добавлена ✓', 'success');
setTimeout(() => hideAlert('create-schedule-alert'), 2000);
renderPreparedSchedules();
updateTableVisibility();
});
// ===== Сохранение в БД =====
btnSaveSchedules.addEventListener('click', async () => {
if (preparedSchedules.length === 0) {
showAlert('save-schedules-alert', 'Нет записей для сохранения', 'error');
return;
}
btnSaveSchedules.disabled = true;
btnSaveSchedules.textContent = 'Сохранение...';
hideAlert('save-schedules-alert');
let errors = 0;
let saved = 0;
const failedRecords = [];
for (const record of preparedSchedules) {
try {
await api.post('/api/department/schedule/create', record);
saved++;
} catch (err) {
console.error('Ошибка сохранения записи:', err);
errors++;
// Помечаем запись как дубликат, если бэк вернул соответствующую ошибку
const isDuplicate = err.status === 409 ||
(err.message && err.message.toLowerCase().includes('уже существует'));
failedRecords.push({
...record,
_errorMsg: isDuplicate
? 'Такая запись уже есть в базе данных'
: (err.message || 'Ошибка сохранения')
});
}
}
btnSaveSchedules.disabled = false;
btnSaveSchedules.textContent = 'Сохранить в БД';
if (errors === 0) {
showAlert('save-schedules-alert', `Все записи (${saved}) успешно сохранены!`, 'success');
preparedSchedules = [];
renderPreparedSchedules();
updateTableVisibility();
setTimeout(closeOverlay, 2000);
} else {
// Оставляем неудачные записи для повторной попытки / удаления
preparedSchedules = failedRecords;
renderPreparedSchedules();
if (saved > 0) {
showAlert('save-schedules-alert',
`Сохранено: ${saved}. Ошибок: ${errors}. Проблемные записи отмечены в таблице.`, 'error');
} else {
showAlert('save-schedules-alert',
`Не удалось сохранить. Ошибок: ${errors}. Проблемные записи отмечены в таблице.`, 'error');
}
}
});
} }

View File

@@ -383,7 +383,7 @@ export async function initUsers() {
const role = document.getElementById('new-role').value; const role = document.getElementById('new-role').value;
const fullName = document.getElementById('new-fullname').value.trim(); const fullName = document.getElementById('new-fullname').value.trim();
const jobTitle = document.getElementById('new-jobtitle').value.trim(); const jobTitle = document.getElementById('new-jobtitle').value.trim();
const department = document.getElementById('new-department').value; const departmentId = document.getElementById('new-department').value;
if (!username || !password || !fullName || !jobTitle || !departmentId) { if (!username || !password || !fullName || !jobTitle || !departmentId) {
showAlert('create-alert', 'Заполните все поля', 'error'); showAlert('create-alert', 'Заполните все поля', 'error');

View File

@@ -1,5 +1,8 @@
<div class="card create-card"> <div class="card create-card">
<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 1rem;">
<h2>Запрос расписания кафедры</h2> <h2>Запрос расписания кафедры</h2>
<button id="btn-create-schedule" class="btn-primary" style="margin-top: 0;">Создать запись</button>
</div>
<form id="department-schedule-form"> <form id="department-schedule-form">
<div class="form-row"> <div class="form-row">
<div class="form-group"> <div class="form-group">
@@ -41,6 +44,143 @@
</form> </form>
</div> </div>
<!-- ===== Общий оверлей для обеих модалок ===== -->
<div class="cs-overlay" id="cs-overlay">
<div class="cs-overlay-scroll">
<!-- Модалка 1: Форма создания записи -->
<div class="cs-modal cs-modal-form card" id="modal-create-schedule">
<div class="cs-modal-header">
<h2>Создать запись (к/ф)</h2>
<button class="btn-close-panel" id="modal-create-schedule-close" title="Закрыть (Esc)">&times;</button>
</div>
<form id="create-schedule-form">
<input type="hidden" id="cs-department-id" value="">
<div class="form-row"
style="align-items: flex-start; gap: 1rem; flex-wrap: wrap; width: 100%; justify-content: space-between;">
<div class="form-group" style="flex: 1 1 180px;">
<label for="cs-period">Учебный год</label>
<select id="cs-period" required>
<option value="">Выберите...</option>
<option value="2024-2025">2024/2025</option>
<option value="2025-2026">2025/2026</option>
<option value="2026-2027">2026/2027</option>
</select>
</div>
<div class="form-group" style="flex: 1 1 180px;">
<label>Семестр</label>
<div style="display: flex; gap: 0.2rem;">
<label class="btn-checkbox">
<input type="radio" name="csSemesterType" value="autumn" required>
<span class="checkbox-btn">Осенний</span>
</label>
<label class="btn-checkbox">
<input type="radio" name="csSemesterType" value="spring" required>
<span class="checkbox-btn">Весенний</span>
</label>
</div>
</div>
<div class="form-group" style="flex: 1 1 150px;">
<label for="cs-semester">Курс/Семестр (номер)</label>
<input type="number" id="cs-semester" required min="1" max="12" placeholder="Например: 1">
</div>
<div class="form-group" style="flex: 1 1 180px;">
<label for="cs-group">Группа</label>
<select id="cs-group" required>
<option value="">Загрузка...</option>
</select>
</div>
<div class="form-group" style="flex: 1 1 180px;">
<label for="cs-subject">Дисциплина</label>
<select id="cs-subject" required>
<option value="">Загрузка...</option>
</select>
</div>
<div class="form-group" style="flex: 1 1 180px;">
<label for="cs-lesson-type">Вид занятий</label>
<select id="cs-lesson-type" required>
<option value="">Выберите тип</option>
<option value="1">Лекция</option>
<option value="2">Практическая работа</option>
<option value="3">Лабораторная работа</option>
</select>
</div>
<div class="form-group" style="flex: 1 1 150px;">
<label for="cs-hours">Часов (семестр)</label>
<input type="number" id="cs-hours" required min="1" max="500" placeholder="Например: 36">
</div>
<div class="form-group" style="flex: 1 1 180px;">
<label>Деление на подгруппы</label>
<div style="display: flex; gap: 0.5rem; align-items: center; height: 42px;">
<label class="btn-checkbox" style="width:100%;">
<input type="checkbox" id="cs-division" value="true">
<span class="checkbox-btn" style="width:100%; text-align:center;">Есть деление</span>
</label>
</div>
</div>
<div class="form-group" style="flex: 1 1 250px;">
<label for="cs-teacher">Преподаватель</label>
<select id="cs-teacher" required>
<option value="">Выберите преподавателя</option>
</select>
</div>
<div class="form-group" style="flex: 0 0 auto; display:flex; align-items: flex-end;">
<button type="submit" class="btn-primary" style="white-space: nowrap;">Добавить в список</button>
</div>
</div>
<div class="form-alert" id="create-schedule-alert" role="alert" style="margin-top: 1rem;"></div>
</form>
</div>
<!-- Модалка 2: Таблица подготовленных записей -->
<div class="cs-modal cs-modal-table card" id="modal-view-schedules" style="display: none;">
<div class="cs-modal-header">
<h2>Подготовленные записи</h2>
<div style="display:flex; gap: 0.75rem; align-items:center;">
<button id="btn-save-schedules" class="btn-primary">Сохранить в БД</button>
</div>
</div>
<div class="form-alert" id="save-schedules-alert" role="alert" style="margin-bottom: 1rem;"></div>
<div class="table-wrap">
<table id="prepared-schedules-table">
<thead>
<tr>
<th>Уч. год</th>
<th>Семестр</th>
<th></th>
<th>Группа</th>
<th>Дисциплина</th>
<th>Вид</th>
<th>Часы</th>
<th>Деление</th>
<th>Преподаватель</th>
<th>Действие</th>
</tr>
</thead>
<tbody id="prepared-schedules-tbody">
<tr>
<td colspan="10" class="loading-row">Нет записей</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
</div>
<div class="table-wrap" id="schedule-blocks-container"> <div class="table-wrap" id="schedule-blocks-container">
<!-- Сгенерированные блоки таблиц будут появляться здесь --> <!-- Сгенерированные блоки таблиц будут появляться здесь -->
</div> </div>

View File

@@ -143,6 +143,7 @@
if (data.token) localStorage.setItem('token', data.token); if (data.token) localStorage.setItem('token', data.token);
if (data.role) localStorage.setItem('role', data.role); if (data.role) localStorage.setItem('role', data.role);
if (data.departmentId) localStorage.setItem('departmentId', data.departmentId);
const redirect = data.redirect || '/'; const redirect = data.redirect || '/';
setTimeout(() => { window.location.href = redirect; }, 400); setTimeout(() => { window.location.href = redirect; }, 400);