diff --git a/.agent/rules/main.md b/.agent/rules/main.md index e2209bc..e4dc0de 100644 --- a/.agent/rules/main.md +++ b/.agent/rules/main.md @@ -8,7 +8,8 @@ trigger: always_on Этот проект представляет собой веб-сайт системы управления университетским расписанием. - **Роль**: Образовательная платформа для управления расписанием. - **Язык**: Смешанный (Java Backend + Web Frontend). -- **Публичный URL**: https://magistr.zuev.company +- **Публичный URL прода**: https://magistr.zuev.company +- **Локальный URL проекта**: localhost:80 ## Структура директорий и обязанности Проект следует определенной структуре папок. Вы должны придерживаться этих путей: diff --git a/frontend/admin/admin.css b/frontend/admin/admin.css index 0e522bd..f178ab8 100644 --- a/frontend/admin/admin.css +++ b/frontend/admin/admin.css @@ -8,45 +8,56 @@ } :root { - --bg-primary: #0f0f1a; - --bg-sidebar: rgba(255, 255, 255, 0.03); - --bg-card: rgba(255, 255, 255, 0.05); - --bg-card-border: rgba(255, 255, 255, 0.08); - --bg-input: rgba(255, 255, 255, 0.06); - --bg-input-focus: rgba(255, 255, 255, 0.1); + /* Deep dark premium background */ + --bg-primary: #0a0a0f; + --bg-sidebar: rgba(255, 255, 255, 0.02); + --bg-card: rgba(255, 255, 255, 0.03); + --bg-card-border: rgba(255, 255, 255, 0.05); + --bg-input: rgba(255, 255, 255, 0.04); + --bg-input-focus: rgba(255, 255, 255, 0.08); --bg-hover: rgba(255, 255, 255, 0.06); - --text-primary: #f0f0f5; - --text-secondary: #9ca3af; - --text-placeholder: #6b7280; - --accent: #6366f1; - --accent-hover: #818cf8; - --accent-glow: rgba(99, 102, 241, 0.35); - --error: #f87171; - --success: #34d399; - --warning: #fbbf24; - --radius-sm: 8px; - --radius-md: 12px; - --transition: 0.2s ease; + + /* Typography */ + --text-primary: #f8fafc; + --text-secondary: #94a3b8; + --text-placeholder: #475569; + + /* Vibrant Accents */ + --accent: #8b5cf6; + --accent-hover: #a78bfa; + --accent-glow: rgba(139, 92, 246, 0.4); + --accent-secondary: #ec4899; + + /* Status Colors */ + --error: #ef4444; + --success: #10b981; + --warning: #f59e0b; + + /* Spatial */ + --radius-sm: 10px; + --radius-md: 16px; + --transition: 0.3s cubic-bezier(0.25, 0.8, 0.25, 1); } /* ===== Light Theme ===== */ [data-theme="light"] { - --bg-primary: #e8eaef; - --bg-sidebar: rgba(255, 255, 255, 0.88); - --bg-card: rgba(255, 255, 255, 0.95); - --bg-card-border: rgba(0, 0, 0, 0.22); - --bg-input: rgba(0, 0, 0, 0.08); - --bg-input-focus: rgba(0, 0, 0, 0.12); - --bg-hover: rgba(0, 0, 0, 0.08); + --bg-primary: #f8fafc; + --bg-sidebar: rgba(255, 255, 255, 0.7); + --bg-card: rgba(255, 255, 255, 0.7); + --bg-card-border: rgba(0, 0, 0, 0.08); + --bg-input: rgba(0, 0, 0, 0.03); + --bg-input-focus: rgba(0, 0, 0, 0.06); + --bg-hover: rgba(0, 0, 0, 0.05); --text-primary: #0f172a; - --text-secondary: #374151; - --text-placeholder: #6b7280; + --text-secondary: #475569; + --text-placeholder: #94a3b8; --accent: #6366f1; --accent-hover: #4f46e5; - --accent-glow: rgba(99, 102, 241, 0.25); - --error: #dc2626; - --success: #16a34a; - --warning: #d97706; + --accent-glow: rgba(99, 102, 241, 0.3); + --accent-secondary: #d946ef; + --error: #ef4444; + --success: #10b981; + --warning: #f59e0b; } [data-theme="light"] .form-group select option, @@ -59,6 +70,10 @@ background: rgba(99, 102, 241, 0.18); } +[data-theme="light"] .custom-multi-select .dropdown-menu { + background: rgba(255, 255, 255, 0.98); +} + [data-theme="light"] .form-group input, [data-theme="light"] .form-group select, [data-theme="light"] .filter-row select { @@ -85,9 +100,11 @@ body { /* ===== Sidebar ===== */ .sidebar { - width: 240px; + width: 260px; min-height: 100vh; background: var(--bg-sidebar); + backdrop-filter: blur(20px); + -webkit-backdrop-filter: blur(20px); border-right: 1px solid var(--bg-card-border); display: flex; flex-direction: column; @@ -96,7 +113,7 @@ body { top: 0; bottom: 0; z-index: 10; - transition: background 0.4s ease, border-color 0.4s ease; + transition: background 0.4s ease, border-color 0.4s ease, transform 0.4s cubic-bezier(0.25, 0.8, 0.25, 1); } .sidebar-header { @@ -121,14 +138,31 @@ body { .nav-item { display: flex; align-items: center; - gap: 0.6rem; - padding: 0.65rem 0.8rem; + gap: 0.75rem; + padding: 0.75rem 1rem; + margin-bottom: 0.25rem; border-radius: var(--radius-sm); color: var(--text-secondary); text-decoration: none; - font-size: 0.9rem; + font-size: 0.95rem; font-weight: 500; - transition: background var(--transition), color var(--transition); + transition: all var(--transition); + position: relative; + overflow: hidden; +} + +.nav-item::before { + content: ''; + position: absolute; + left: 0; + top: 0; + bottom: 0; + width: 3px; + background: var(--accent); + border-radius: 0 4px 4px 0; + transform: scaleY(0); + transition: transform var(--transition); + opacity: 0; } .nav-item:hover { @@ -137,12 +171,23 @@ body { transform: translateX(4px); } +.nav-item.active { + background: rgba(139, 92, 246, 0.12); + color: var(--accent-hover); +} + +.nav-item.active::before { + transform: scaleY(1); + opacity: 1; +} + .nav-item svg { transition: transform var(--transition); } -.nav-item:hover svg { - transform: scale(1.1); +.nav-item:hover svg, +.nav-item.active svg { + transform: scale(1.15) rotate(-5deg); } /* Checkbox list styling */ @@ -247,7 +292,7 @@ body { /* ===== Main ===== */ .main { flex: 1; - margin-left: 240px; + margin-left: 260px; min-height: 100vh; } @@ -290,11 +335,38 @@ body { .card { background: var(--bg-card); + backdrop-filter: blur(20px); + -webkit-backdrop-filter: blur(20px); border: 1px solid var(--bg-card-border); border-radius: var(--radius-md); - padding: 1.5rem; - transition: background 0.4s ease, border-color 0.4s ease; - animation: slideUpCard 0.4s ease-out both; + padding: 1.75rem; + position: relative; + overflow: visible; + transition: all 0.4s cubic-bezier(0.25, 0.8, 0.25, 1); + animation: slideUpCard 0.5s cubic-bezier(0.25, 0.8, 0.25, 1) both; + box-shadow: 0 4px 20px rgba(0, 0, 0, 0.05); +} + +.card::before { + content: ''; + position: absolute; + top: 0; + left: 0; + right: 0; + height: 1px; + background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.1), transparent); + opacity: 0; + transition: opacity var(--transition); +} + +.card:hover { + transform: translateY(-4px); + box-shadow: 0 12px 30px rgba(0, 0, 0, 0.1); + border-color: rgba(255, 255, 255, 0.12); +} + +.card:hover::before { + opacity: 1; } /* Staggered cards */ @@ -310,6 +382,11 @@ body { animation-delay: 0.3s; } +/* Specific Cards */ +.create-card { + z-index: 10; +} + .card h2 { font-size: 0.8rem; font-weight: 600; @@ -345,26 +422,43 @@ body { .form-group input, .form-group select { width: 100%; - padding: 0.65rem 0.8rem; + padding: 0.75rem 1rem; background: var(--bg-input); - border: 1px solid transparent; + border: 1px solid var(--bg-card-border); border-radius: var(--radius-sm); color: var(--text-primary); font-family: inherit; - font-size: 0.9rem; + font-size: 0.95rem; outline: none; - transition: background var(--transition), border-color var(--transition), box-shadow var(--transition); + transition: all var(--transition); } .form-group input::placeholder { color: var(--text-placeholder); + transition: opacity var(--transition); } .form-group input:focus, .form-group select:focus { background: var(--bg-input-focus); border-color: var(--accent); - box-shadow: 0 0 0 3px var(--accent-glow); + box-shadow: 0 0 0 4px var(--accent-glow); + transform: translateY(-1px); +} + +.form-group input:focus::placeholder { + opacity: 0.5; +} + +/* Hide Number Arrows */ +input[type="number"]::-webkit-outer-spin-button, +input[type="number"]::-webkit-inner-spin-button { + -webkit-appearance: none; + margin: 0; +} + +input[type="number"] { + -moz-appearance: textfield; } .form-group select { @@ -382,23 +476,48 @@ body { } .btn-create { - padding: 0.65rem 1.5rem; - background: linear-gradient(135deg, var(--accent), #8b5cf6); + position: relative; + overflow: hidden; + padding: 0.75rem 1.75rem; + background: linear-gradient(135deg, var(--accent), var(--accent-secondary)); border: none; border-radius: var(--radius-sm); color: #fff; font-family: inherit; - font-size: 0.9rem; + font-size: 0.95rem; font-weight: 600; + letter-spacing: 0.02em; cursor: pointer; white-space: nowrap; - transition: transform var(--transition), box-shadow var(--transition); - box-shadow: 0 2px 10px var(--accent-glow); + transition: all var(--transition); + box-shadow: 0 4px 15px var(--accent-glow); +} + +.btn-create::before { + content: ''; + position: absolute; + top: 0; + left: 0; + right: 0; + bottom: 0; + background: linear-gradient(rgba(255, 255, 255, 0.2), transparent); + border-radius: inherit; + opacity: 0; + transition: opacity var(--transition); } .btn-create:hover { - transform: translateY(-1px); - box-shadow: 0 4px 16px var(--accent-glow); + transform: translateY(-2px); + box-shadow: 0 8px 25px var(--accent-glow); +} + +.btn-create:hover::before { + opacity: 1; +} + +.btn-create:active { + transform: translateY(1px); + box-shadow: 0 2px 10px var(--accent-glow); } @keyframes slideDownAlert { @@ -439,7 +558,7 @@ body { /* ===== Table ===== */ .table-wrap { - overflow-x: auto; + overflow-x: visible; } table { @@ -459,9 +578,22 @@ thead th { } tbody td { - padding: 0.7rem 0.8rem; - font-size: 0.9rem; - border-bottom: 1px solid rgba(255, 255, 255, 0.03); + padding: 0.85rem 1rem; + font-size: 0.95rem; + border-bottom: 1px solid var(--bg-card-border); + transition: background var(--transition); +} + +@keyframes slideInRow { + from { + opacity: 0; + transform: translateY(10px); + } + + to { + opacity: 1; + transform: translateY(0); + } } @keyframes slideInRow { @@ -683,6 +815,122 @@ tbody tr:hover { } /* ===== Theme Toggle Button ===== */ +.theme-toggle { + width: 40px; + height: 40px; + border: none; + border-radius: 50%; + background: var(--bg-card); + border: 1px solid var(--bg-card-border); + color: var(--text-primary); + cursor: pointer; + display: flex; + align-items: center; + justify-content: center; + backdrop-filter: blur(12px); + -webkit-backdrop-filter: blur(12px); + transition: all var(--transition); + z-index: 100; + flex-shrink: 0; +} + +.theme-toggle svg { + width: 20px; + height: 20px; + transition: transform 0.4s ease; +} + +.theme-toggle:hover { + transform: scale(1.1); + box-shadow: 0 4px 16px var(--accent-glow); +} + +.theme-toggle:active { + transform: scale(0.95); +} + +/* ===== Custom Multi Select ===== */ +.custom-multi-select { + position: relative; + width: 100%; +} + +.custom-multi-select .select-box { + width: 100%; + padding: 0.75rem 1rem; + background: var(--bg-input); + border: 1px solid var(--bg-card-border); + border-radius: var(--radius-sm); + color: var(--text-primary); + font-family: inherit; + font-size: 0.95rem; + cursor: pointer; + display: flex; + align-items: center; + justify-content: space-between; + transition: all var(--transition); +} + +.custom-multi-select .select-box.active { + background: var(--bg-input-focus); + border-color: var(--accent); + box-shadow: 0 0 0 4px var(--accent-glow); +} + +.custom-multi-select .dropdown-icon { + transition: transform var(--transition); +} + +.custom-multi-select .select-box.active .dropdown-icon { + transform: rotate(180deg); +} + +.custom-multi-select .dropdown-menu { + position: absolute; + top: calc(100% + 5px); + left: 0; + width: 100%; + background: rgba(15, 15, 26, 0.98); + backdrop-filter: blur(24px); + -webkit-backdrop-filter: blur(24px); + border: 1px solid var(--bg-card-border); + border-radius: var(--radius-md); + box-shadow: 0 10px 30px rgba(0, 0, 0, 0.2); + padding: 0.75rem; + z-index: 9999; + opacity: 0; + visibility: hidden; + transform: translateY(-10px); + transition: all var(--transition); + max-height: 250px; + overflow-y: auto; +} + +.custom-multi-select .dropdown-menu.open { + opacity: 1; + visibility: visible; + transform: translateY(0); +} + +.checkbox-group-vertical { + display: flex; + flex-direction: column; + gap: 8px; +} + +.checkbox-group-vertical .checkbox-item { + padding: 6px 8px; + border-radius: 6px; + transition: background var(--transition); + display: flex; + align-items: center; + gap: 8px; + cursor: pointer; +} + +.checkbox-group-vertical .checkbox-item:hover { + background: var(--bg-hover); +} /* ===== Modals ===== */ .modal-overlay { diff --git a/frontend/admin/admin.js b/frontend/admin/admin.js index 599935c..af4a7f6 100644 --- a/frontend/admin/admin.js +++ b/frontend/admin/admin.js @@ -73,6 +73,56 @@ const createEquipmentAlert = document.getElementById('create-equipment-alert'); const equipmentCheckboxes = document.getElementById('equipment-checkboxes'); + // --- Multi-select logic --- + function updateSelectText(containerId, textId) { + const container = document.getElementById(containerId); + const textEl = document.getElementById(textId); + if (!container || !textEl) return; + const checked = Array.from(container.querySelectorAll('input:checked')); + if (checked.length === 0) { + textEl.textContent = 'Выберите оборудование...'; + } else if (checked.length === 1) { + textEl.textContent = checked[0].parentElement.textContent.trim(); + } else { + textEl.textContent = `Выбрано: ${checked.length}`; + } + } + + function initMultiSelect(boxId, menuId, textId, checkboxContainerId) { + const box = document.getElementById(boxId); + const menu = document.getElementById(menuId); + const container = document.getElementById(checkboxContainerId); + if (!box || !menu || !container) return; + + box.addEventListener('click', (e) => { + e.stopPropagation(); + const isOpen = menu.classList.contains('open'); + document.querySelectorAll('.dropdown-menu').forEach(m => m.classList.remove('open')); + document.querySelectorAll('.select-box').forEach(b => b.classList.remove('active')); + if (!isOpen) { + menu.classList.add('open'); + box.classList.add('active'); + } + }); + + menu.addEventListener('click', (e) => { + e.stopPropagation(); + }); + + container.addEventListener('change', () => { + updateSelectText(checkboxContainerId, textId); + }); + } + + 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'); + + document.addEventListener('click', () => { + document.querySelectorAll('.dropdown-menu').forEach(m => m.classList.remove('open')); + document.querySelectorAll('.select-box').forEach(b => b.classList.remove('active')); + }); + // -------------------------- + const navItems = document.querySelectorAll('.nav-item[data-tab]'); const tabContents = document.querySelectorAll('.tab-content'); @@ -427,6 +477,7 @@ ${escapeHtml(eq.name)} `).join(''); + updateSelectText('equipment-checkboxes', 'equipment-select-text'); } createEquipmentForm.addEventListener('submit', async (e) => { @@ -541,6 +592,7 @@ if (res.ok) { showAlert(createClassroomAlert, `Аудитория "${data.name}" добавлена`, 'success'); createClassroomForm.reset(); + updateSelectText('equipment-checkboxes', 'equipment-select-text'); loadClassrooms(); } else { showAlert(createClassroomAlert, data.message || 'Ошибка создания', 'error'); @@ -611,6 +663,7 @@ } else { editEquipmentCheckboxes.innerHTML = '
Нет доступного оборудования
'; } + updateSelectText('edit-equipment-checkboxes', 'edit-equipment-select-text'); hideAlert(editClassroomAlert); modalEditClassroom.classList.add('open'); diff --git a/frontend/admin/index.html b/frontend/admin/index.html index fffe4ee..41b3e09 100644 --- a/frontend/admin/index.html +++ b/frontend/admin/index.html @@ -292,12 +292,24 @@ -Загрузка...
+