# Компонент FilterSort - Полная документация

## Назначение компонента

`FilterSort` - универсальный компонент для фильтрации и сортировки данных в приложении. Предоставляет интуитивно понятный интерфейс для применения множественных фильтров, сортировки результатов и управления состоянием данных. Компонент полностью настраиваемый и поддерживает различные типы фильтров.

## Импорт и регистрация

```coffee
# В основном файле приложения (app/temp.coffee)
components:
    'filter-sort': require 'app/shared/FilterSort'
```

## Базовое использование

### 1. Простая фильтрация
```pug
filter-sort(
    :filters="filtersConfig"
    :sort-options="sortOptions"
    @filter-change="handleFilterChange"
    @sort-change="handleSortChange"
)
```

### 2. С предустановленными значениями
```pug
filter-sort(
    :filters="filtersConfig"
    :sort-options="sortOptions"
    :initial-filters="{ category: 'music', status: 'active' }"
    :initial-sort="'date_desc'"
    @change="handleFilterSortChange"
)
```

## Полный список props

### `filters` (обязательный)
- **Тип:** `Array<Object>`
- **Описание:** Конфигурация фильтров
- **Структура фильтра:**
  ```coffee
  {
    key: 'category',           # Уникальный ключ фильтра
    type: 'select',           # Тип фильтра: select, multiselect, range, date, search
    label: 'Категория',       # Отображаемое название
    options: [                # Опции для select/multiselect
      { value: 'music', label: 'Музыка' },
      { value: 'art', label: 'Искусство' }
    ],
    placeholder: 'Выберите категорию',  # Плейсхолдер
    multiple: false,          # Множественный выбор
    searchable: true         # Поиск в опциях
  }
  ```

### `sortOptions` (обязательный)
- **Тип:** `Array<Object>`
- **Описание:** Опции сортировки
- **Структура:**
  ```coffee
  [
    { value: 'date_asc', label: 'Дата (сначала старые)' },
    { value: 'date_desc', label: 'Дата (сначала новые)' },
    { value: 'title_asc', label: 'Название (А-Я)' },
    { value: 'title_desc', label: 'Название (Я-А)' }
  ]
  ```

### `initialFilters` (опциональный)
- **Тип:** `Object`
- **По умолчанию:** `{}`
- **Описание:** Начальные значения фильтров
- **Пример:**
  ```coffee
  {
    category: 'music',
    price: { min: 100, max: 500 },
    status: ['active', 'upcoming']
  }
  ```

### `initialSort` (опциональный)
- **Тип:** `String`
- **По умолчанию:** Первая опция из `sortOptions`
- **Описание:** Начальная сортировка

### `debounceDelay` (опциональный)
- **Тип:** `Number`
- **По умолчанию:** `300`
- **Описание:** Задержка debounce для событий изменения (мс)

### `showReset` (опциональный)
- **Тип:** `Boolean`
- **По умолчанию:** `true`
- **Описание:** Показывать кнопку сброса фильтров

### `compact` (опциональный)
- **Тип:** `Boolean`
- **По умолчанию:** `false`
- **Описание:** Компактный режим отображения

### `mobileFriendly` (опциональный)
- **Тип:** `Boolean`
- **По умолчанию:** `true`
- **Описание:** Адаптивность для мобильных устройств

## События (Events)

### `@filter-change`
- **Описание:** Изменение значений фильтров
- **Payload:** 
  ```coffee
  {
    filters: Object,      # Текущие значения всех фильтров
    changedFilter: String # Ключ измененного фильтра
  }
  ```

### `@sort-change`
- **Описание:** Изменение сортировки
- **Payload:** `String` - значение выбранной сортировки

### `@change`
- **Описание:** Общее событие изменения (фильтры + сортировка)
- **Payload:**
  ```coffee
  {
    filters: Object,  # Текущие значения фильтров
    sort: String,     # Текущая сортировка
    hasActiveFilters: Boolean  # Есть ли активные фильтры
  }
  ```

### `@reset`
- **Описание:** Сброс всех фильтров и сортировки
- **Payload:** `null`

## Слоты (Slots)

### `[filter-{key}]` (кастомные фильтры)
- **Описание:** Слот для кастомной реализации фильтра
- **Props:**
  ```coffee
  {
    filter: Object,      # Конфигурация фильтра
    value: Any,          # Текущее значение
    update: Function     # Функция обновления значения
  }
  ```
- **Пример:**
  ```pug
  template([filter-custom]="{ filter, value, update }")
      div(class="custom-filter")
          input(
              :value="value"
              @input="update($event.target.value)"
              :placeholder="filter.placeholder"
          )
  ```

### `[before-filters]`
- **Описание:** Контент перед блоком фильтров
- **Пример:**
  ```pug
  template([before-filters])
      div(class="text-sm text-gray-500 mb-4") Используйте фильтры для уточнения результатов
  ```

### `[after-filters]`
- **Описание:** Контент после блока фильтров
- **Пример:**
  ```pug
  template([after-filters])
      div(class="text-xs text-gray-400 mt-2") Найдено результатов: {{ totalCount }}
  ```

### `[sort-label]`
- **Описание:** Кастомная метка для селектора сортировки
- **Props:** `{ currentSort: Object }`
- **Пример:**
  ```pug
  template([sort-label]="{ currentSort }")
      span Сортировка: {{ currentSort.label }}
  ```

## Типы фильтров

### 1. Select (одиночный выбор)
```coffee
{
  key: 'category',
  type: 'select',
  label: 'Категория',
  options: [
    { value: 'music', label: 'Музыка' },
    { value: 'art', label: 'Искусство' }
  ],
  placeholder: 'Выберите категорию'
}
```

### 2. Multiselect (множественный выбор)
```coffee
{
  key: 'tags',
  type: 'multiselect',
  label: 'Теги',
  multiple: true,
  options: [
    { value: 'classical', label: 'Классика' },
    { value: 'jazz', label: 'Джаз' }
  ]
}
```

### 3. Range (диапазон значений)
```coffee
{
  key: 'price',
  type: 'range',
  label: 'Цена',
  min: 0,
  max: 1000,
  step: 50,
  unit: 'TJS'
}
```

### 4. Date (дата)
```coffee
{
  key: 'event_date',
  type: 'date',
  label: 'Дата мероприятия',
  format: 'YYYY-MM-DD'
}
```

### 5. Search (поиск)
```coffee
{
  key: 'search',
  type: 'search',
  label: 'Поиск',
  placeholder: 'Введите запрос...'
}
```

### 6. Boolean (логический)
```coffee
{
  key: 'featured',
  type: 'boolean',
  label: 'Только избранные',
  trueLabel: 'Да',
  falseLabel: 'Нет'
}
```

## Примеры использования

### 1. Фильтрация мероприятий
```pug
filter-sort(
    :filters="eventFilters"
    :sort-options="eventSortOptions"
    :initial-filters="initialEventFilters"
    @change="handleEventsFilterChange"
    class="mb-6"
)
```

```coffee
data: ->
    eventFilters: [
        {
            key: 'category'
            type: 'select'
            label: 'Категория'
            options: [
                { value: 'music', label: 'Музыка' },
                { value: 'art', label: 'Искусство' },
                { value: 'theater', label: 'Театр' }
            ]
        },
        {
            key: 'price_range'
            type: 'range'
            label: 'Цена'
            min: 0
            max: 1000
            step: 100
            unit: 'TJS'
        },
        {
            key: 'date'
            type: 'date'
            label: 'Дата'
        },
        {
            key: 'status'
            type: 'multiselect'
            label: 'Статус'
            options: [
                { value: 'upcoming', label: 'Предстоящие' },
                { value: 'ongoing', label: 'Текущие' }
            ]
        }
    ]
    
    eventSortOptions: [
        { value: 'date_asc', label: 'Дата (сначала старые)' },
        { value: 'date_desc', label: 'Дата (сначала новые)' },
        { value: 'price_asc', label: 'Цена (по возрастанию)' },
        { value: 'price_desc', label: 'Цена (по убыванию)' },
        { value: 'title_asc', label: 'Название (А-Я)' }
    ]
    
    initialEventFilters:
        status: ['upcoming']

methods:
    handleEventsFilterChange: ({ filters, sort, hasActiveFilters }) ->
        # Загрузка отфильтрованных данных
        @loadEvents(filters, sort)
```

### 2. Фильтрация товаров
```pug
filter-sort(
    :filters="productFilters"
    :sort-options="productSortOptions"
    @filter-change="handleProductFilterChange"
    compact
)
```

```coffee
data: ->
    productFilters: [
        {
            key: 'category'
            type: 'select'
            label: 'Категория'
            options: [
                { value: 'clothing', label: 'Одежда' },
                { value: 'souvenirs', label: 'Сувениры' },
                { value: 'music', label: 'Музыка' }
            ]
        },
        {
            key: 'size'
            type: 'multiselect'
            label: 'Размер'
            options: [
                { value: 's', label: 'S' },
                { value: 'm', label: 'M' },
                { value: 'l', label: 'L' }
            ]
        },
        {
            key: 'price'
            type: 'range'
            label: 'Цена'
            min: 0
            max: 500
            step: 50
        },
        {
            key: 'in_stock'
            type: 'boolean'
            label: 'В наличии'
        }
    ]
```

### 3. Поиск в блоге
```pug
filter-sort(
    :filters="blogFilters"
    :sort-options="blogSortOptions"
    @change="handleBlogFilterChange"
    mobile-friendly
)
```

```coffee
data: ->
    blogFilters: [
        {
            key: 'search'
            type: 'search'
            label: 'Поиск'
            placeholder: 'Поиск по статьям...'
        },
        {
            key: 'author'
            type: 'select'
            label: 'Автор'
            options: [
                { value: 'admin', label: 'Администрация' },
                { value: 'editor', label: 'Редактор' }
            ]
        },
        {
            key: 'tags'
            type: 'multiselect'
            label: 'Теги'
            options: [
                { value: 'news', label: 'Новости' },
                { value: 'events', label: 'События' },
                { value: 'culture', label: 'Культура' }
            ]
        }
    ]
```

### 4. Кастомный фильтр через слот
```pug
filter-sort(:filters="customFilters" @change="handleChange")
    template([filter-custom]="{ filter, value, update }")
        div(class="custom-rating-filter")
            h4 {{ filter.label }}
            div(class="rating-stars")
                span(
                    v-for="star in [1,2,3,4,5]"
                    :key="star"
                    @click="update(star)"
                    :class="{ 'text-yellow-400': star <= value }"
                    class="cursor-pointer"
                ) ★
    
    template([before-filters])
        div(class="bg-blue-50 p-4 rounded-lg mb-4")
            h3(class="text-lg font-semibold") Уточните поиск
            p Используйте фильтры для точного поиска
```

## Методы компонента (refs)

### `reset()`
- **Описание:** Сброс всех фильтров и сортировки
- **Пример:**
  ```pug
  filter-sort(ref="filterComponent" :filters="filters")
  button(@click="$refs.filterComponent.reset()") Сбросить
  ```

### `getCurrentState()`
- **Описание:** Получение текущего состояния фильтров и сортировки
- **Возвращает:**
  ```coffee
  {
    filters: Object,
    sort: String,
    hasActiveFilters: Boolean
  }
  ```

### `setFilter(key, value)`
- **Описание:** Установка значения конкретного фильтра
- **Параметры:** `key` (String), `value` (Any)
- **Пример:**
  ```coffee
  @$refs.filterComponent.setFilter('category', 'music')
  ```

### `clearFilter(key)`
- **Описание:** Очистка конкретного фильтра
- **Параметр:** `key` (String)

## Интеграция с загрузкой данных

### 1. Использование с AppDB
```coffee
methods:
    loadFilteredEvents: (filters, sort) ->
        try
            options = {}
            
            # Преобразование фильтров для базы данных
            if filters.category
                options.category = filters.category
            
            if filters.price_range
                options.minPrice = filters.price_range.min
                options.maxPrice = filters.price_range.max
            
            if filters.search
                options.searchQuery = filters.search
            
            # Добавление сортировки
            options.sort = sort
            
            @events = await AppDB.getEvents(options)
            
        catch error
            debug.log "Ошибка загрузки мероприятий: "+error
```

### 2. Дебаунс для производительности
```coffee
data: ->
    filterTimeout: null

methods:
    handleFilterChange: ({ filters, sort }) ->
        # Отмена предыдущего таймера
        if @filterTimeout
            clearTimeout(@filterTimeout)
        
        # Установка нового таймера
        @filterTimeout = setTimeout(=>
            @loadData(filters, sort)
        , 500)
```

## Стилизация и кастомизация

### CSS классы для кастомизации
```styl
.filter-sort
    // Основной контейнер
    &__container
        @apply bg-white rounded-lg shadow-sm border
    
    // Группа фильтров
    &__filters
        @apply p-4 space-y-4
    
    // Отдельный фильтр
    &__filter
        @apply space-y-2
    
    // Метка фильтра
    &__label
        @apply block text-sm font-medium text-gray-700
    
    // Контролы фильтра
    &__control
        @apply w-full rounded-md border-gray-300 shadow-sm
    
    // Блок сортировки
    &__sort
        @apply border-t border-gray-200 px-4 py-3 bg-gray-50
    
    // Кнопка сброса
    &__reset
        @apply text-sm text-blue-600 hover:text-blue-800
```

### Темная тема
```styl
@media (prefers-color-scheme: dark)
    .filter-sort
        &__container
            @apply bg-gray-800 border-gray-700
        
        &__label
            @apply text-gray-300
        
        &__control
            @apply bg-gray-700 border-gray-600 text-white
        
        &__sort
            @apply border-gray-700 bg-gray-900
```

## Best Practices

### 1. Оптимизация производительности
```coffee
# Используйте debounce для частых изменений
filter-sort(
    :filters="filters"
    :debounce-delay="500"
    @change="handleFilterChange"
)

# Ленивая загрузка опций
{
    key: 'category',
    type: 'select',
    label: 'Категория',
    options: async () => await loadCategories()  # Функция загрузки
}
```

### 2. Валидация фильтров
```coffee
# Проверка перед применением
handleFilterChange: ({ filters }) ->
    if filters.price_range?.min > filters.price_range?.max
        @showError('Минимальная цена не может быть больше максимальной')
        return
    
    @applyFilters(filters)
```

### 3. Сохранение состояния
```coffee
# Сохранение в localStorage
handleFilterChange: ({ filters, sort }) ->
    localStorage.setItem('eventFilters', JSON.stringify(filters))
    localStorage.setItem('eventSort', sort)

# Восстановление при загрузке
created: ->
    savedFilters = localStorage.getItem('eventFilters')
    if savedFilters
        @initialFilters = JSON.parse(savedFilters)
    
    savedSort = localStorage.getItem('eventSort')
    if savedSort
        @initialSort = savedSort
```

### 4. Доступность (a11y)
```pug
filter-sort(
    :filters="filters"
    aria-label="Фильтрация и сортировка контента"
)
    template([before-filters])
        h2(id="filter-heading" class="sr-only") Фильтрация и сортировка
```

Компонент FilterSort предоставляет мощный и гибкий инструмент для управления фильтрацией и сортировкой данных в приложении. Благодаря модульной архитектуре и богатым возможностям кастомизации, он может быть адаптирован для любых сценариев использования - от простых списков до сложных систем поиска.
