diff --git a/frontend/admin/css/department.css b/frontend/admin/css/department.css new file mode 100644 index 0000000..691860e --- /dev/null +++ b/frontend/admin/css/department.css @@ -0,0 +1,235 @@ +.wrap{ + max-width: 900px; + margin: 0 auto; + background: var(--bg-card); + border: 1px solid var(--bg-card-border); + border-radius: 12px; + overflow: hidden; + box-shadow: 0 6px 20px rgba(0,0,0,.06); +} + +.header{ + padding: 14px 16px; + border-bottom: 1px solid var(--bg-card-border); + font-weight: 700; + color: var(--text-primary); +} + +details.table-item{ + border-top: 1px solid var(--bg-card-border); +} +details.table-item:first-of-type{ border-top:none; } + +summary{ + list-style: none; + cursor: pointer; + user-select: none; + padding: 12px 16px; + display: flex; + align-items: center; + gap: 10px; +} +summary::-webkit-details-marker{ display:none; } + +.chev{ + width: 28px; + height: 28px; + border: 1px solid var(--bg-card-border); + border-radius: 10px; + display: grid; + place-items: center; + flex: 0 0 auto; + + color: var(--text-secondary); + background: var(--bg-input); + + transition: transform .18s ease, color .18s ease, border-color .18s ease, background .18s ease; +} + +.chev-icon{ + width: 16px; + height: 16px; + display: block; +} + +summary:hover .chev{ + background: var(--bg-hover); + border-color: color-mix(in srgb, var(--accent) 22%, var(--bg-card-border)); + color: var(--text-primary); +} + +details[open] .chev{ + transform: rotate(180deg); + color: var(--accent); + border-color: color-mix(in srgb, var(--accent) 35%, var(--bg-card-border)); + background: color-mix(in srgb, var(--accent) 10%, var(--bg-input)); +} + +.meta{ color: var(--text-secondary); font-size: 12px; } + +.content{ padding: 0 16px 16px 16px; } + +.wrap table{ + width: 100%; + border-collapse: collapse; + border: 1px solid var(--bg-card-border); + border-radius: 10px; + overflow: hidden; + background: var(--bg-card); +} + +.wrap thead th{ + text-align: left; + font-size: 13px; + color: var(--text-secondary); + background: var(--bg-input); + border-bottom: 1px solid var(--bg-card-border); + padding: 10px 12px; +} + +.wrap tbody td{ + padding: 10px 12px; + border-bottom: 1px solid var(--bg-card-border); + font-size: 14px; + color: var(--text-primary); +} + +.wrap tbody tr:hover{ background: var(--bg-hover); } + +.title-multiline{ + display: flex; + flex-direction: column; + gap: 2px; + line-height: 1.2; +} + +.title-multiline .title-main{ + font-weight: 700; + color: var(--text-primary); +} + +.title-multiline .title-sub{ + font-weight: 500; + font-size: 12px; + color: var(--text-secondary); +} + +.title-multiline b{ + font-weight: 700; + color: var(--text-primary); +} + +/* summary = 3 колонки: [chev] [title] [meta] */ +details.table-item > summary{ + display: grid; + grid-template-columns: 28px 1fr auto; + gap: 12px; + align-items: start; /* важно: всё прижимаем к верху */ + padding: 12px 16px; +} + +/* чтобы текст нормально переносился и не растягивал мету */ +details.table-item > summary .title{ + min-width: 0; /* важно для grid, иначе может распирать */ +} + +/* "2 записи" всегда справа и сверху, аккуратно */ +details.table-item > summary .meta{ + justify-self: end; + align-self: start; + white-space: nowrap; + padding-top: 4px; /* чуть опустить относительно первой строки */ + font-size: 12px; + color: var(--text-secondary); +} + +/* стрелка тоже сверху */ +details.table-item > summary .chev{ + align-self: start; + margin-top: 2px; +} + +.records-search{ + width: min(360px, 60vw); + padding: 0.45rem 0.7rem; + background: var(--bg-input); + border: 1px solid var(--bg-card-border); + border-radius: var(--radius-sm); + color: var(--text-primary); + font-size: 0.9rem; + outline: none; + transition: border-color .2s ease, box-shadow .2s ease, background .2s ease; +} + +.records-search::placeholder{ color: var(--text-placeholder); } + +.records-search:focus{ + background: var(--bg-input-focus); + border-color: var(--accent); + box-shadow: 0 0 0 3px var(--accent-glow); +} +/* Таблица внутри раскрывающегося блока */ +details.table-item .content table{ + width: 100%; + border-collapse: separate; /* нужно для красивых линий */ + border-spacing: 0; + border: 1px solid var(--bg-card-border); + border-radius: 12px; + overflow: hidden; + background: var(--bg-card); +} + +/* Шапка */ +details.table-item .content thead th{ + position: sticky; /* опционально: шапка прилипает при скролле */ + top: 0; + z-index: 1; + + background: var(--bg-input); + color: var(--text-secondary); + border-bottom: 1px solid var(--bg-card-border); +} + +/* Ячейки: одинаковые отступы */ +details.table-item .content th, +details.table-item .content td{ + padding: 0.75rem 0.85rem; + vertical-align: top; +} + +/* Вертикальные разделители между колонками */ +details.table-item .content th:not(:last-child), +details.table-item .content td:not(:last-child){ + border-right: 1px solid var(--bg-card-border); +} + +/* Горизонтальные разделители между строками */ +details.table-item .content tbody td{ + border-bottom: 1px solid var(--bg-card-border); + color: var(--text-primary); +} + +/* У последней строки нет нижней линии */ +details.table-item .content tbody tr:last-child td{ + border-bottom: none; +} + +/* "Зебра" для читабельности */ +details.table-item .content tbody tr:nth-child(even){ + background: color-mix(in srgb, var(--bg-card) 70%, var(--bg-hover)); +} + +/* Ховер по строке */ +details.table-item .content tbody tr:hover{ + background: var(--bg-hover); +} + +/* (Опционально) Чтобы длинный текст не ломал ширину */ +details.table-item .content td{ + word-break: break-word; +} + +/* (Опционально) если таблица широкая — пусть скроллится горизонтально */ +details.table-item .content{ + overflow-x: auto; +} \ No newline at end of file diff --git a/frontend/admin/index.html b/frontend/admin/index.html index 0bbddc0..7ddec41 100755 --- a/frontend/admin/index.html +++ b/frontend/admin/index.html @@ -14,6 +14,7 @@ + @@ -46,6 +47,17 @@ Пользователи + + + + + + + + + Кафедра + diff --git a/frontend/admin/js/main.js b/frontend/admin/js/main.js index 235203e..6745859 100755 --- a/frontend/admin/js/main.js +++ b/frontend/admin/js/main.js @@ -9,6 +9,7 @@ import { initClassrooms } from './views/classrooms.js'; import { initSubjects } from './views/subjects.js'; import {initSchedule} from "./views/schedule.js"; import {initDatabase} from "./views/database.js"; +import {initDepartment} from "./views/department.js"; // Configuration const ROUTES = { @@ -20,6 +21,7 @@ const ROUTES = { subjects: { title: 'Дисциплины и преподаватели', file: 'views/subjects.html', init: initSubjects }, schedule: { title: 'Расписание занятий', file: 'views/schedule.html', init: initSchedule }, database: { title: 'База данных', file: 'views/database.html', init: initDatabase }, + department: { title: 'Кафедры', file: 'views/department.html', init: initDepartment }, }; let currentTab = null; diff --git a/frontend/admin/js/views/department.js b/frontend/admin/js/views/department.js new file mode 100644 index 0000000..b7c3711 --- /dev/null +++ b/frontend/admin/js/views/department.js @@ -0,0 +1,4 @@ +import { api } from '../api.js'; +import { escapeHtml } from '../utils.js'; + +export async function initDepartment() { } \ No newline at end of file diff --git a/frontend/admin/js/views/subjects.js b/frontend/admin/js/views/subjects.js index 2978419..c31abbe 100755 --- a/frontend/admin/js/views/subjects.js +++ b/frontend/admin/js/views/subjects.js @@ -24,19 +24,21 @@ export async function initSubjects() { renderSubjects(allSubjects); populateSubjectSelect(allSubjects); } catch (e) { - if (subjectsTbody) subjectsTbody.innerHTML = '
Ошибка загрузки
'; + if (subjectsTbody) subjectsTbody.innerHTML = '
Ошибка загрузки
'; } } function renderSubjects(subjects) { if (!subjects || !subjects.length) { - subjectsTbody.innerHTML = '
Нет дисциплин
'; + subjectsTbody.innerHTML = '
Нет дисциплин
'; return; } subjectsTbody.innerHTML = subjects.map(s => `
${s.id} ${escapeHtml(s.name)} + ${escapeHtml(s.code || '-')} + ${s.departmentId || '-'}
`).join(''); } @@ -100,11 +102,19 @@ export async function initSubjects() { e.preventDefault(); hideAlert('create-subject-alert'); const name = document.getElementById('new-subject-name').value.trim(); + const code = document.getElementById('new-subject-code').value.trim(); + const departmentId = document.getElementById('new-subject-department').value; if (!name) { showAlert('create-subject-alert', 'Введите название', 'error'); return; } + if (!code) { showAlert('create-subject-alert', 'Введите код предмета', 'error'); return; } + if (!departmentId) { showAlert('create-subject-alert', 'Введите идентификатор кафедры', 'error'); return; } try { - const data = await api.post('/api/subjects', { name }); - showAlert('create-subject-alert', `Дисциплина "${escapeHtml(data.name)}" добавлена`, 'success'); + const data = await api.post('/api/subjects', { + name, + code, + departmentId: Number(departmentId) + }); + showAlert('create-subject-alert', `Дисциплина "${escapeHtml(data.name || name)}" добавлена`, 'success'); createSubjectForm.reset(); loadSubjects(); } catch (e) { showAlert('create-subject-alert', e.message || 'Ошибка создания', 'error'); } diff --git a/frontend/admin/views/department.html b/frontend/admin/views/department.html new file mode 100644 index 0000000..74dc1bf --- /dev/null +++ b/frontend/admin/views/department.html @@ -0,0 +1,193 @@ +
+

Кафедра

+ +
+ + + +
+
+ + +
+ + +
+ + +
+ Данные к составлению расписания + Кафедра: Информационная безопасность + Факультет: ФиПИ + Семестр: весенний + Уч. год: 2024/2025 +
+
3 записи
+
+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
СпециальностьКурс и семестрГруппаДисциплинаВид занятийЧасов в неделюДеление на подгруппыФамилия преподавателя
09.02.072 курс, 4 семестрИС-21Базы данныхЛабораторная2ДаИванов
09.02.072 курс, 4 семестрИС-22Операционные системыПрактика1НетСмирнов
09.02.071 курс, 2 семестрИС-12АлгоритмыЛекция2НетКузнецов
+
+
+ + +
+ + +
orders
+
1 запись
+
+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
СпециальностьКурс и семестрГруппаДисциплинаВид занятийЧасов в неделюДеление на подгруппыФамилия преподавателя
38.02.011 курс, 1 семестрЭК-11ЭкономикаЛекция1НетПетров
+
+
+ + +
+ + +
products
+
2 записи
+
+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
СпециальностьКурс и семестрГруппаДисциплинаВид занятийЧасов в неделюДеление на подгруппыФамилия преподавателя
15.02.083 курс, 6 семестрМС-31МатериаловедениеПрактика3ДаСидоров
15.02.083 курс, 6 семестрМС-32Технология металловЛабораторная2ДаОрлов
+
+
+ +
+ \ No newline at end of file diff --git a/frontend/admin/views/subjects.html b/frontend/admin/views/subjects.html index a312430..167edaa 100755 --- a/frontend/admin/views/subjects.html +++ b/frontend/admin/views/subjects.html @@ -7,6 +7,14 @@ +
+ + +
+
+ + +
@@ -43,12 +51,14 @@ ID Название + Код предмета + Кафедра (ID) Действия - Загрузка... + Загрузка...