Files
vidconf/frontend/src/styles/admin.css

151 lines
14 KiB
CSS
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/*
* VidConf — стили страницы «Администрирование».
* Табличные/toolbar-паттерны — 1:1 из design/mockups/admin.html
* (.section-tab/.toolbar-row/.search-wrap/.table-card/.badge-role-*).
* Вкладка «Настройки» в макете отсутствует — построена по тому же
* визуальному языку (.settings-card повторяет .table-card/.invite-card).
* page-shell/page-main/page-head — общие из lobby.css; кнопки, модалки и
* переключатель switch — из shell.css и calendar.css.
*
* Все правила скопированы под `.admin-page`, чтобы избежать конфликта имён
* классов (`.icon-btn`, `.chip`) с другими светлыми страницами оболочки —
* в проекте уже был прецедент такой коллизии (см. комментарий в
* my-conferences.css про порядок правил в исходнике).
*/
.admin-page .section-tabs { display: flex; gap: var(--space-2); margin-bottom: var(--space-5); border-bottom: 1px solid var(--color-border); }
.admin-page .section-tab { padding: 10px 4px; margin-right: var(--space-6); background: none; border: none; border-bottom: 2px solid transparent; font: var(--text-body-lg); font-weight: 600; color: var(--color-ink-500); cursor: pointer; }
.admin-page .section-tab:hover { color: var(--color-ink-900); }
.admin-page .section-tab.is-active { color: var(--color-ink-700); border-color: var(--color-accent-active); }
.admin-page .section-tabs-inline { display: flex; flex-wrap: wrap; gap: 8px; margin-bottom: var(--space-4); }
.admin-page .chip { display: inline-flex; align-items: center; gap: 6px; padding: 8px 16px; border-radius: var(--radius-full); border: 1.5px solid var(--color-border); background: var(--color-surface); font: var(--text-body); font-weight: 600; color: var(--color-ink-500); cursor: pointer; }
.admin-page .chip.is-active { border-color: var(--color-ink-700); color: var(--color-ink-700); background: var(--color-bg-alt); }
.admin-page .toolbar-row { display: flex; align-items: center; justify-content: space-between; margin-bottom: var(--space-5); gap: var(--space-4); flex-wrap: wrap; }
/* Группа «поиск + кнопка действия» слева в toolbar-row («Добавить
пользователя») — без неё третий элемент в flex-контейнере с
justify-content: space-between распределился бы отдельной колонкой, а не
встал рядом с поиском. */
.admin-page .toolbar-left { display: flex; align-items: center; gap: var(--space-3); flex-wrap: wrap; }
.admin-page .search-wrap { position: relative; width: 320px; max-width: 100%; }
.admin-page .search-wrap .icon { position: absolute; left: 12px; top: 50%; transform: translateY(-50%); color: var(--color-ink-400); }
/* color/::placeholder — намеренно НЕ в тёмном скоупе: раньше их не было
вовсе, текст/плейсхолдер рисовались UA-дефолтом браузера, а не хардкодом
светлой темы, — в светлой это давало почти совпадающий с ink-900/ink-300
результат, а в тёмной ломалось (тёмный UA-текст на тёмном фоне). var()
резолвится в обеих темах сам, поэтому одно правило на все три скоупа
корректно и для светлой, и для тёмной — оборачивать не в чем и незачем. */
.admin-page .search-wrap input { width: 100%; font: var(--text-body); color: var(--color-ink-900); padding: 9px 12px 9px 38px; border-radius: var(--radius-full); border: 1px solid var(--color-border); background: var(--color-surface); }
.admin-page .search-wrap input::placeholder { color: var(--color-ink-300); }
.admin-page .search-wrap input:focus { outline: none; border-color: var(--color-focus-ring); }
.admin-page .toolbar-count { font: var(--text-body); color: var(--color-ink-500); white-space: nowrap; }
.admin-page .table-card { background: var(--color-surface); border: 1px solid var(--color-border); border-radius: var(--radius-2xl); overflow: hidden; margin-bottom: var(--space-6); }
.admin-page table { width: 100%; border-collapse: collapse; }
.admin-page thead th { text-align: left; background: var(--color-bg-alt); font: var(--text-caption); color: var(--color-ink-500); padding: 12px 20px; text-transform: uppercase; }
.admin-page tbody td { padding: 14px 20px; font: var(--text-body-lg); color: var(--color-ink-900); border-top: 1px solid var(--color-border); }
.admin-page tbody tr:hover { background: var(--color-bg-alt); }
.admin-page .badge-role-admin { background: var(--color-ink-700); color: #fff; }
.admin-page .badge-role-user { background: var(--color-bg-alt); color: var(--color-ink-500); }
.admin-page .row-actions { display: flex; gap: 6px; }
.admin-page .icon-btn { width: 32px; height: 32px; border-radius: var(--radius-md); border: 1px solid var(--color-border); background: var(--color-surface); color: var(--color-ink-500); display: flex; align-items: center; justify-content: center; cursor: pointer; flex-shrink: 0; }
.admin-page .icon-btn:hover:not(:disabled) { border-color: var(--color-border-strong); color: var(--color-ink-900); }
.admin-page .icon-btn.danger:hover:not(:disabled) { border-color: var(--color-danger); color: var(--color-danger); background: var(--color-danger-bg); }
.admin-page .icon-btn:disabled { opacity: .45; cursor: not-allowed; }
.admin-page .user-cell { display: flex; align-items: center; gap: 10px; }
.admin-page .user-cell .avatar { width: 32px; height: 32px; font-size: 12px; }
.admin-page .user-cell div span { display: block; font: 13px var(--font-body); color: var(--color-ink-500); }
/* Клик по имени открывает карточку профиля — визуально
остаётся обычным текстом (не похоже на ссылку), подчёркивание — только hover. */
.admin-page .user-cell-name { background: none; border: none; padding: 0; font: inherit; color: inherit; cursor: pointer; text-align: left; }
.admin-page .user-cell-name:hover { text-decoration: underline; color: var(--color-ink-700); }
.admin-page select { font: var(--text-body); color: var(--color-ink-900); padding: 6px 10px; border-radius: var(--radius-md); border: 1px solid var(--color-border); background: var(--color-surface); font-family: var(--font-body); }
.admin-page select:disabled { opacity: .5; cursor: not-allowed; }
.admin-page select:focus { outline: none; border-color: var(--color-focus-ring); }
.admin-page .pagination-row { display: flex; justify-content: center; gap: var(--space-3); margin-bottom: var(--space-8); }
/* ---------- Вкладка «Команды» (нет в макете — паттерн toolbar-row/table-card) ---------- */
/* color/::placeholder — то же обоснование, что у .search-wrap input выше. */
.admin-page .team-add-form { display: flex; align-items: center; gap: var(--space-3); flex-wrap: wrap; }
.admin-page .team-add-form input { width: 280px; max-width: 100%; font: var(--text-body); color: var(--color-ink-900); padding: 9px 14px; border-radius: var(--radius-full); border: 1px solid var(--color-border); background: var(--color-surface); }
.admin-page .team-add-form input::placeholder { color: var(--color-ink-300); }
.admin-page .team-add-form input:focus { outline: none; border-color: var(--color-focus-ring); }
.admin-page .team-rename-form input { width: 240px; max-width: 100%; font: var(--text-body-lg); color: var(--color-ink-900); padding: 6px 10px; border-radius: var(--radius-md); border: 1px solid var(--color-border-strong); background: var(--color-surface); }
.admin-page .team-rename-form input:focus { outline: none; border-color: var(--color-focus-ring); }
/* ---------- Вкладка «Настройки» (нет в макете — паттерн table-card/invite-card) ---------- */
/* Соседние карточки внутри строки грида должны быть одной высоты: без
align-items:stretch (значение по умолчанию для grid) каждая плитка
сжималась бы по своему контенту, и при разном наполнении («Модули» с
четырьмя переключателями против правой колонки, «Рассылка саммари»
против «Часового пояса») ряд выглядел рваным. */
.admin-page .settings-grid { display: grid; grid-template-columns: repeat(2, 1fr); gap: var(--space-5); }
.admin-page .settings-card { display: flex; flex-direction: column; background: var(--color-surface); border: 1px solid var(--color-border); border-radius: var(--radius-2xl); padding: var(--space-6); }
.admin-page .settings-card h2 { font: var(--text-h2); margin: 0 0 4px; color: var(--color-ink-900); }
.admin-page .settings-card .desc { font: var(--text-body); color: var(--color-ink-500); margin: 0 0 var(--space-4); }
.admin-page .settings-actions { grid-column: 1 / -1; display: flex; justify-content: flex-end; }
/* Первый ряд грида: левая плитка «Модули» (grid-item, растягивается по
умолчанию align-items:stretch) и правая колонка — вертикальный стек из
«Уровень AI» и «Эталон mail-домена». Стек как grid-item тоже растягивается
до высоты ряда; внутри него flex-grow на карточках распределяет остаток
высоты поровну между двумя правыми плитками, поэтому итоговая высота
левой и суммарная высота правых двух совпадают без фиксации пикселей.
Важно: flex-basis только auto и flex-shrink:0 — карточка не должна
ужиматься ниже высоты своего контента (список уровней AI/подпись+инпут),
иначе при `flex-basis: 0` контент переполняет сжатый бокс и визуально
наезжает на соседнюю плитку снизу (overflow: visible по умолчанию не
обрезает переполнение, а просто рисует поверх соседа). */
.admin-page .settings-stack { display: flex; flex-direction: column; gap: var(--space-5); }
.admin-page .settings-stack .settings-card { flex: 1 0 auto; }
/* Инпут эталонного домена дизейблится при выключенном тумблере верификации,
но значение поля при этом не сбрасывается (сохраняется в стейте формы). */
.admin-page .settings-card .field input:disabled { opacity: .55; cursor: not-allowed; background: var(--color-bg-alt); }
/* Тело карточки занимает всё оставшееся место после заголовка/описания —
именно за счёт flex:1 плитка ниже соседней по контенту получает
аккуратно распределённую пустоту, а не пустой хвост снизу. */
.admin-page .settings-card-body { display: flex; flex-direction: column; flex: 1; }
.admin-page .settings-card-body--spread { justify-content: space-between; }
.admin-page .settings-card-body--center { justify-content: center; }
.admin-page .ai-level-list { display: flex; flex-direction: column; gap: var(--space-3); }
.admin-page .ai-level-option { display: flex; align-items: flex-start; gap: 10px; padding: var(--space-3); border: 1px solid var(--color-border); border-radius: var(--radius-md); cursor: pointer; }
.admin-page .ai-level-option:hover { border-color: var(--color-border-strong); }
.admin-page .ai-level-option.is-disabled { cursor: not-allowed; opacity: .6; }
.admin-page .ai-level-option input[type="radio"] { margin-top: 3px; width: 16px; height: 16px; accent-color: var(--color-ink-700); flex-shrink: 0; }
.admin-page .ai-level-option strong { display: block; font: var(--text-body); font-weight: 600; color: var(--color-ink-900); }
/* align-items: flex-start (не center) — причины недоступности
Блок 1) могут быть длинными («модель X не скачана — запустите install.sh
с пресетом N») и переноситься на несколько строк; при center иконка
съезжала бы к середине блока текста, а не к его первой строке. */
.admin-page .ai-level-reason { display: flex; align-items: flex-start; gap: 5px; font: var(--text-caption); color: var(--color-warning); margin-top: 4px; }
.admin-page .ai-level-reason svg { flex-shrink: 0; margin-top: 1px; }
@media (max-width: 960px) {
.admin-page .settings-grid { grid-template-columns: 1fr; }
}
/* ---------- Футер админки ----------
Версия инстанса из `/api/health` (см. AdminPage.tsx) — до появления поля
на backend строка сокращается до одного «VidConf» без версии. */
.admin-page .admin-footer { margin-top: var(--space-6); padding-top: var(--space-4); border-top: 1px solid var(--color-border); font: var(--text-caption); color: var(--color-ink-400); text-align: center; }
/* =====================================================================
* Тёмная тема: заливка var(--color-ink-700) (индиго) сочеталась с жёстким
* color:#fff; в тёмной теме ink-700 = мята (светлый тон) — белый текст
* перестаёт читаться, заменено на var(--color-accent-text) (тёмный, для
* заливки мятой). См. design/mockups/dark/README.md, категория 1.
* ===================================================================== */
@media (prefers-color-scheme: dark) {
:root:not([data-theme]) .admin-page .badge-role-admin { color: var(--color-accent-text); }
}
:root[data-theme='dark'] .admin-page .badge-role-admin { color: var(--color-accent-text); }