Gogs пре 3 недеља
родитељ
комит
df464b47e6
2 измењених фајлова са 632 додато и 1 уклоњено
  1. 1 1
      README.md
  2. 631 0
      vue/app/shared/FilterSort/README.md

+ 1 - 1
README.md

@@ -1,5 +1,5 @@
 # Текущая задача
-Опиши компонент для возможности использования в разработке: https://gogs.osvoj.ru/s5l.ru/borbad.s5l.ru/src/master/vue/app/shared/FilterSort
+Опиши компонент для возможности использования в разработке: https://gogs.osvoj.ru/s5l.ru/borbad.s5l.ru/src/master/vue/app/shared/ModalWindow
 сделай полное описание вех методов использования и взаимодействия. (в описание не дублируй листинги из репозитария)
 
 # файл с правилами

+ 631 - 0
vue/app/shared/FilterSort/README.md

@@ -0,0 +1,631 @@
+# Компонент 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 предоставляет мощный и гибкий инструмент для управления фильтрацией и сортировкой данных в приложении. Благодаря модульной архитектуре и богатым возможностям кастомизации, он может быть адаптирован для любых сценариев использования - от простых списков до сложных систем поиска.