Corp/backend/static/css/components/page-header.css
Игорь Надеин eb70384928 � Мобильная оптимизация и новый тип заявления 'Отгул'
MOBILE FIXES:
- request-list.css: переделан мобильный layout с flexbox вместо grid
- page-header.css: оптимизирован padding/margin для мобильных экранов
- requestListHandler.js:
  * Добавлен AbortController с таймаутом (10/15 сек для мобильных)
  * Обработка offline/online событий сети
  * Информативные сообщения об ошибках (отсутствие интернета, таймаут)
  * Адаптивный Intersection Observer (rootMargin 200px для мобильных)
  * Лучшая обработка network failures

UI/UX:
- Скрыт счётчик на экранах < 480px
- Контент теперь отображается на всех разрешениях
- Исчезновение спиннера при ошибке сети
- Визуальное различие между ошибками загрузки

NEW FEATURE:
- Добавлен новый тип заявления 'Отгул' (day_off)
- Обновлена миграция 0009_alter_request_type
- Добавлен во все формы создания/редактирования
- Добавлен в фильтры списка
2025-12-29 10:00:32 +03:00

393 lines
9.7 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.

/**
* Page Header Component
*
* Универсальная шапка для страниц списков с:
* - Полупрозрачным фоном (sticky positioning)
* - Иконкой и заголовком
* - Счётчиком элементов
* - Переключателем области (Мои/Все)
* - Кнопкой фильтров
* - Кнопкой создания (вытянутая на десктопе, иконка на мобильных)
* - Поиском iOS-стиль
*
* Использование:
* {% include "includes/components/page_header.html" with ... %}
* <link rel="stylesheet" href="{% static 'css/components/page-header.css' %}">
*/
/* ===== ОСНОВНОЙ КОНТЕЙНЕР ШАПКИ ===== */
.page-header {
position: sticky;
top: var(--navbar-h, 60px);
z-index: 10;
padding: var(--space-2) 0; /* 8px сверху/снизу */
margin-bottom: var(--space-3); /* 12px отступ снизу */
/* Полупрозрачный фон с градиентом */
background: linear-gradient(
to bottom,
color-mix(in srgb, var(--bs-body-bg) 90%, transparent) 0%,
color-mix(in srgb, var(--bs-body-bg) 85%, transparent) 30%,
color-mix(in srgb, var(--bs-body-bg) 70%, transparent) 50%,
color-mix(in srgb, var(--bs-body-bg) 40%, transparent) 70%,
transparent 100%
);
/* Убрана border-bottom - линия не нужна */
/* Анимация появления при скролле */
transition: background 0.2s ease, box-shadow 0.2s ease;
}
/* Псевдоэлемент для градиентного размытия */
.page-header::before {
content: '';
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
backdrop-filter: saturate(180%) blur(12px);
-webkit-backdrop-filter: saturate(180%) blur(12px);
mask-image: linear-gradient(
to bottom,
black 0%,
black 30%,
rgba(0, 0, 0, 0.7) 50%,
rgba(0, 0, 0, 0.4) 70%,
transparent 100%
);
-webkit-mask-image: linear-gradient(
to bottom,
black 0%,
black 30%,
rgba(0, 0, 0, 0.7) 50%,
rgba(0, 0, 0, 0.4) 70%,
transparent 100%
);
pointer-events: none;
z-index: -1;
}
/* Контент должен быть выше псевдоэлемента */
.page-header > * {
position: relative;
z-index: 1;
}
/* При скролле - более выраженный фон */
.page-header.scrolled {
background: linear-gradient(
to bottom,
color-mix(in srgb, var(--bs-body-bg) 95%, transparent) 0%,
color-mix(in srgb, var(--bs-body-bg) 90%, transparent) 30%,
color-mix(in srgb, var(--bs-body-bg) 75%, transparent) 50%,
color-mix(in srgb, var(--bs-body-bg) 45%, transparent) 70%,
transparent 100%
);
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.04);
}
/* ===== ЗАГОЛОВОК ===== */
.page-title {
font-weight: 800;
font-size: 1.75rem;
letter-spacing: -0.01em;
line-height: 1.2;
color: var(--bs-body-color);
flex-shrink: 0;
}
/* ===== СЧЁТЧИК ===== */
.page-count {
font-size: 0.875rem;
color: var(--bs-secondary-color);
white-space: nowrap;
flex-shrink: 0;
}
.page-count .count-value {
font-weight: 600;
color: var(--bs-body-color);
}
/* ===== ПЕРЕКЛЮЧАТЕЛЬ ОБЛАСТИ (МОИ/ВСЕ) ===== */
.page-scope-switch {
flex-shrink: 0;
}
.page-scope-switch .btn {
min-width: 60px;
font-size: 0.875rem;
border-radius: 999px;
padding: 0.375rem 0.75rem;
transition: all 0.2s ease;
}
.page-scope-switch .btn:first-child {
border-top-right-radius: 0;
border-bottom-right-radius: 0;
}
.page-scope-switch .btn:last-child {
border-top-left-radius: 0;
border-bottom-left-radius: 0;
}
.page-scope-switch .btn.active {
background-color: var(--bs-primary);
border-color: var(--bs-primary);
color: white;
font-weight: 600;
}
/* ===== КНОПКА ФИЛЬТРОВ ===== */
/* Использует класс .btn-circle из buttons.css */
.page-filter-toggle:hover {
background-color: color-mix(in srgb, var(--bs-primary), transparent 85%);
border-color: var(--bs-primary);
color: var(--bs-primary);
}
.page-filter-toggle[aria-expanded="true"] {
background-color: var(--bs-primary);
border-color: var(--bs-primary);
color: white;
}
/* ===== КНОПКА СОЗДАНИЯ ===== */
/* Использует классы .btn-pill и .btn-icon из buttons.css */
.page-create-btn {
font-weight: 600;
font-size: 0.875rem;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
transition: all 0.2s ease;
}
.page-create-btn:hover {
transform: translateY(-1px);
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.15);
}
.page-create-btn:active {
transform: translateY(0);
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.1);
}
/* ===== ПОИСК ===== */
.page-search {
flex-shrink: 1;
min-width: 200px;
max-width: 400px;
flex-grow: 1;
}
/* На мобильных поиск на всю ширину */
@media (max-width: 991.98px) {
.page-search {
flex-basis: 100%;
max-width: none;
margin-top: var(--space-2);
order: 10;
}
/* Кнопка фильтров переносится под поиск и прижимается к правому краю */
.page-filter-toggle {
order: 11;
margin-top: var(--space-2);
margin-left: auto;
margin-right: 0 !important;
}
/* Родительский контейнер должен переносить элементы */
.page-header .d-flex.align-items-center {
flex-wrap: wrap;
}
}
/* ===== БЛОК ФИЛЬТРОВ ===== */
.page-filters-collapse {
margin-top: var(--space-2);
width: 100%;
}
.page-filters {
border-radius: var(--radius-lg, 14px);
border: 1px solid color-mix(in srgb, var(--bs-border-color), transparent 10%);
background: var(--bs-body-bg);
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08);
padding: var(--space-3);
animation: slideDown 0.3s ease;
}
/* Адаптивность кнопок внутри фильтров */
.page-filters .btn {
white-space: nowrap;
min-width: fit-content;
}
.page-filters .d-flex.gap-2 {
flex-wrap: wrap;
}
@keyframes slideDown {
from {
opacity: 0;
transform: translateY(-10px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
/* ===== АДАПТИВНОСТЬ ===== */
/* Планшеты */
@media (max-width: 991.98px) {
.page-title {
font-size: 1.5rem;
}
.page-header {
padding: var(--space-2) 0;
}
}
/* Мобильные */
@media (max-width: 575.98px) {
.page-header {
margin-bottom: var(--space-2);
padding: var(--space-1) 0;
top: var(--navbar-h, 60px);
}
.page-title {
font-size: 1.125rem;
font-weight: 700;
}
.page-count {
font-size: 0.7rem;
}
.page-scope-switch {
gap: 0.25rem;
}
.page-scope-switch .btn {
min-width: auto;
font-size: 0.7rem;
padding: 0.2rem 0.4rem;
}
.page-filters {
margin-top: 0.5rem;
}
.page-filters .btn {
padding: 0.4rem 0.75rem;
font-size: 0.85rem;
}
.page-search {
margin-top: 0.5rem;
}
/* Скрываем счётчик на очень маленьких экранах */
@media (max-width: 480px) {
.page-count {
display: none;
}
.page-header .card {
margin-top: 0;
width: 100%;
}
.page-header .card button.dept-row,
.page-header .card a.dept-row {
padding: var(--space-1) var(--space-1);
font-size: 0.9rem;
}
}
}
/* ===== ИНТЕГРАЦИЯ С IOS-SEARCH ===== */
/* ios-search уже определён в ios-search.css, здесь только позиционирование */
.page-search .ios-search {
width: 100%;
}
/* ===== КНОПКА СОЗДАНИЯ В ВИДЕ КАРТОЧКИ (для модала) ===== */
/* Кнопка должна выглядеть как ссылка в card */
.page-header .card {
margin-top: var(--space-2);
width: 100%;
}
.page-header .card button.dept-row,
.page-header .card a.dept-row {
cursor: pointer;
transition: background-color 0.2s ease;
width: 100%;
padding: var(--space-1) var(--space-2);
text-align: center;
}
/* Текст кнопки должен быть как card-title */
.page-header .card button.dept-row .card-title,
.page-header .card a.dept-row .card-title {
font-weight: 700;
color: var(--bs-body-color);
}
/* Иконка должна быть синей как в оригинале */
.page-header .card button.dept-row i,
.page-header .card a.dept-row i {
color: var(--bs-primary);
}
.page-header .card button.dept-row:hover,
.page-header .card a.dept-row:hover {
background-color: var(--bs-light);
}
@media (prefers-color-scheme: dark) {
.page-header .card button.dept-row:hover,
.page-header .card a.dept-row:hover {
background-color: color-mix(in srgb, var(--bs-body-bg) 95%, white);
}
}
/* ===== ТЕМНАЯ ТЕМА ===== */
@media (prefers-color-scheme: dark) {
.page-header {
background: linear-gradient(
to bottom,
color-mix(in srgb, var(--bs-body-bg) 85%, transparent) 0%,
color-mix(in srgb, var(--bs-body-bg) 80%, transparent) 45%,
color-mix(in srgb, var(--bs-body-bg) 65%, transparent) 50%,
color-mix(in srgb, var(--bs-body-bg) 35%, transparent) 70%,
transparent 100%
);
border-bottom-color: color-mix(in srgb, var(--bs-border-color), transparent 60%);
}
.page-header.scrolled {
background: linear-gradient(
to bottom,
color-mix(in srgb, var(--bs-body-bg) 90%, transparent) 0%,
color-mix(in srgb, var(--bs-body-bg) 80%, transparent) 30%,
color-mix(in srgb, var(--bs-body-bg) 72%, transparent) 50%,
color-mix(in srgb, var(--bs-body-bg) 40%, transparent) 70%,
transparent 100%
);
}
.page-filters {
box-shadow: 0 2px 12px rgba(0, 0, 0, 0.3);
}
}