Gogs пре 3 недеља
родитељ
комит
6ce5eb1650

+ 8 - 1
README.md

@@ -119,7 +119,7 @@ globalThis.EventBus = new AppEventBus()
 следи за строгим соблюдением синтаксиса используемых языков (coffeescript, pug, stylus)
 в pug не используй многострочные вычисляемые атрибуты
 
-## работа с консолью и текстовыми константами/переменными
+## работа с консолью и текстовыми константами/переменными используй конкатенацию (СТРОГО! ВАЖНО!)
 используй для вывода в консоль debug.log
 console.log "переменная temp = #{temp}" - не правильно
 debug.log "переменная temp = "+temp     - правильно
@@ -127,12 +127,19 @@ debug.log "переменная temp = "+temp     - правильно
 shareUrl = "https://twitter.com/intent/tweet?text=#{text}" - не правильно
 shareUrl = "https://twitter.com/intent/tweet?text="+text   - правильно
 
+"#{@baseUrl}/#{@dbName}/_all_docs?include_docs=true" - не правильно
+@baseUrl+"/"+@dbName+"/_all_docs?include_docs=true" - правильно
+
 ## Цвета и управление темами
 во всех *.styl файлах используй цвета в виде переменных, определённых в файле tailwind.config.js
 
 ## Стили
 все глобальный настройки стилей, пиши в app/temp.styl в остальных файлах, строго стили необходимые только для данного компаонента
 
+## Расположение статических файлов/изображений
+img(src="/images/hall-interior.jpg") - не правильно
+img(src="/assets/[domenName]/hall-interior.jpg") - правильно 
+
 ## стиль написания классов 
 .container.mx-auto.px-4 - не правильно
 div(class="container.mx-auto  px-4") - правильно

+ 158 - 0
vue/app/core/CouchdbClass.coffee

@@ -0,0 +1,158 @@
+class CouchDBService
+    constructor: ->
+        @baseUrl = 'http://localhost:5984'
+        @dbName = 'kohi_borbad_events'
+        @headers = 
+            'Content-Type': 'application/json'
+            'Authorization': 'Basic ' + btoa('admin:password')
+        @testData = [        {
+                    _id: 'event_classical_001'
+                    title: 'Концерт симфонического оркестра'
+                    date: '2025-10-15'
+                    time: '19:00'
+                    description: 'Произведения Чайковского и Рахманинова в исполнении Национального симфонического оркестра'
+                    image: '/images/event-classical.jpg'
+                    category: 'classical'
+                    price: 50
+                    venue: 'Большой зал'
+                    duration: '2 часа 30 минут'
+                    ageRestriction: '12+'
+                    availableTickets: 45
+                    isFeatured: true
+                    inSlider: true
+                    shortDescription: 'Шедевры классической музыки'
+                    cta: 'Купить билеты'
+                }
+                {
+                    _id: 'event_folk_001'
+                    title: 'Вечер таджикской народной музыки'
+                    date: '2025-10-20'
+                    time: '18:30'
+                    description: 'Выступление фольклорного ансамбля "Шашмаком" с программой традиционных мелодий и танцев'
+                    image: '/images/event-folk.jpg'
+                    category: 'folk'
+                    price: 30
+                    venue: 'Малый зал'
+                    duration: '2 часа'
+                    ageRestriction: '6+'
+                    availableTickets: 28
+                    isFeatured: true
+                    inSlider: true
+                    shortDescription: 'Традиционные мелодии и танцы Таджикистана'
+                    cta: 'Узнать больше'
+                }
+                {
+                    _id: 'event_jazz_001'
+                    title: 'Джазовый фестиваль "Borbad Jazz"'
+                    date: '2025-10-25'
+                    time: '20:00'
+                    description: 'Международные джазовые коллективы из Европы и Азии в уникальной акустике зала'
+                    image: '/images/event-jazz.jpg'
+                    category: 'jazz'
+                    price: 70
+                    venue: 'Большой зал'
+                    duration: '3 часа'
+                    ageRestriction: '16+'
+                    availableTickets: 15
+                    isFeatured: true
+                    inSlider: true
+                    shortDescription: 'Международные джазовые коллективы'
+                    cta: 'Смотреть расписание'
+                }
+                {
+                    _id: 'event_pop_001'
+                    title: 'Концерт популярной музыки'
+                    date: '2025-10-28'
+                    time: '19:30'
+                    description: 'Лучшие поп-исполнители Таджикистана представят новые хиты и классические композиции'
+                    image: '/images/event-pop.jpg'
+                    category: 'pop'
+                    price: 45
+                    venue: 'Большой зал'
+                    duration: '2 часа 15 минут'
+                    ageRestriction: '12+'
+                    availableTickets: 67
+                    isFeatured: true
+                    inSlider: false
+                    shortDescription: 'Лучшие поп-исполнители страны'
+                    cta: 'Купить билеты'
+                }
+                {
+                    _id: 'event_opera_001'
+                    title: 'Оперный гала-концерт'
+                    date: '2025-11-02'
+                    time: '18:00'
+                    description: 'Известные оперные певцы исполнят арии из мировых шедевров оперного искусства'
+                    image: '/images/event-opera.jpg'
+                    category: 'classical'
+                    price: 80
+                    venue: 'Большой зал'
+                    duration: '2 часа 45 минут'
+                    ageRestriction: '12+'
+                    availableTickets: 23
+                    isFeatured: true
+                    inSlider: false
+                    shortDescription: 'Арии из мировых оперных шедевров'
+                    cta: 'Забронировать места'
+                }
+                {
+                    _id: 'event_dance_001'
+                    title: 'Танцевальное шоу "Восточные ритмы"'
+                    date: '2025-11-05'
+                    time: '19:00'
+                    description: 'Традиционные и современные танцевальные коллективы представят красочное шоу'
+                    image: '/images/event-dance.jpg'
+                    category: 'dance'
+                    price: 35
+                    venue: 'Большой зал'
+                    duration: '2 часа 30 минут'
+                    ageRestriction: '6+'
+                    availableTickets: 89
+                    isFeatured: true
+                    inSlider: true
+                    shortDescription: 'Традиционные и современные танцы'
+                    cta: 'Посмотреть программу'
+                }
+            ]
+    getAllEvents: ->
+        new Promise (resolve, reject) =>
+            try
+                response = await fetch("#{@baseUrl}/#{@dbName}/_all_docs?include_docs=true", {
+                    method: 'GET'
+                    headers: @headers
+                })
+                
+                if response.ok
+                    data = await response.json()
+                    events = data.rows.map (row) -> row.doc
+                    resolve(events.filter (event) -> !event._id.startsWith('_design/'))
+                else
+                    debug.log "Ошибка получения мероприятий: "+response.statusText
+                    resolve(@testData)
+            catch error
+                debug.log "Ошибка подключения к CouchDB: "+error
+                resolve(@testData)
+
+    getEventById: (id) ->
+        new Promise (resolve, reject) =>
+            try
+                response = await fetch("#{@baseUrl}/#{@dbName}/#{id}", {
+                    method: 'GET'
+                    headers: @headers
+                })
+                
+                if response.ok
+                    event = await response.json()
+                    resolve(event)
+                else
+                    debug.log "Ошибка получения мероприятия: "+response.statusText
+                    resolve(@testData.filter (a)->
+                                 (a.id == id)?true:false
+                    )
+            catch error
+                debug.log "Ошибка подключения к CouchDB: "+error
+                resolve(@testData.filter (a)->
+                       (a.id == id)?true:false
+                )
+
+module.exports = CouchDBService

+ 66 - 0
vue/app/pages/EventDetail/index.coffee

@@ -0,0 +1,66 @@
+document.head.insertAdjacentHTML('beforeend','<style type="text/tailwindcss">'+stylFns['app/pages/EventDetail/index.styl']+'</style>')
+
+module.exports =
+    name: 'EventDetail'
+    render: (new Function '_ctx', '_cache', renderFns['app/pages/EventDetail/index.pug'])()
+    data: ->
+        event: null
+        loading: true
+        categoryLabels:
+            classical: 'Классическая музыка'
+            folk: 'Фольклор'
+            jazz: 'Джаз'
+            pop: 'Поп-музыка'
+            dance: 'Танцевальное шоу'
+    mounted: ->
+        @loadEvent()
+    methods:
+        loadEvent: ->
+            eventId = @$route.params.id
+            _.couchDBService.getEventById(eventId)
+            .then (event) =>
+                @event = event
+            .catch (error) =>
+                debug.log "Ошибка загрузки мероприятия: "+error
+                @event = null
+            .finally =>
+                @loading = false
+
+        getCategoryLabel: (category) ->
+            @categoryLabels[category] || category
+
+        getCategoryBadgeClass: (category) ->
+            classes =
+                classical: 'bg-blue-500'
+                folk: 'bg-green-500'
+                jazz: 'bg-purple-500'
+                pop: 'bg-pink-500'
+                dance: 'bg-orange-500'
+            
+            classes[category] || 'bg-gray-500'
+
+        formatDateTime: (dateString, timeString) ->
+            try
+                date = new Date(dateString)
+                options = { 
+                    weekday: 'long',
+                    year: 'numeric', 
+                    month: 'long', 
+                    day: 'numeric'
+                }
+                formattedDate = date.toLocaleDateString('ru-RU', options)
+                formattedDate + ", " + timeString
+            catch
+                dateString + ", " + timeString
+
+        bookTickets: ->
+            if @event?.availableTickets > 0
+                _.openModal('SuccessModal', {
+                    title: 'Билет забронирован!'
+                    content: "Вы успешно забронировали билет на \""+@event.title+"\". Подробности отправлены на вашу почту."
+                })
+            else
+                _.openModal('SuccessModal', {
+                    title: 'Билеты распроданы'
+                    content: 'К сожалению, все билеты на это мероприятие уже распроданы.'
+                })

+ 66 - 0
vue/app/pages/EventDetail/index.pug

@@ -0,0 +1,66 @@
+section(class="min-h-screen bg-gray-50 dark:bg-gray-900 py-8")
+    div(class="container mx-auto px-4")
+        div(v-if="loading" class="text-center")
+            div(class="animate-spin rounded-full h-12 w-12 border-b-2 border-accent mx-auto")
+            p(class="text-gray-600 dark:text-gray-400 mt-4") Загрузка мероприятия...
+        
+        div(v-else-if="event" class="max-w-4xl mx-auto")
+            div(class="bg-white dark:bg-gray-800 rounded-2xl shadow-lg overflow-hidden")
+                div(class="relative")
+                    img(:src="event.image" :alt="event.title" class="w-full h-64 md:h-80 object-cover")
+                    div(class="absolute top-4 right-4")
+                        span(
+                            :class="getCategoryBadgeClass(event.category)"
+                            class="px-3 py-1 rounded-full text-xs font-bold text-white"
+                        ) {{ getCategoryLabel(event.category) }}
+                
+                div(class="p-8")
+                    div(class="flex flex-col md:flex-row md:justify-between md:items-start mb-6")
+                        div(class="mb-4 md:mb-0")
+                            h1(class="text-3xl md:text-4xl font-bold text-gray-800 dark:text-white mb-2") {{ event.title }}
+                            p(class="text-xl text-accent font-semibold") {{ event.price }} сомони
+                        
+                        div(class="text-right")
+                            p(class="text-lg font-semibold text-gray-800 dark:text-white") {{ formatDateTime(event.date, event.time) }}
+                            p(class="text-gray-600 dark:text-gray-400") {{ event.venue }}
+                    
+                    div(class="grid grid-cols-1 md:grid-cols-3 gap-6 mb-8")
+                        div(class="text-center p-4 bg-gray-50 dark:bg-gray-700 rounded-lg")
+                            div(class="text-2xl font-bold text-accent mb-1") {{ event.availableTickets }}
+                            div(class="text-sm text-gray-600 dark:text-gray-400") Осталось билетов
+                        
+                        div(class="text-center p-4 bg-gray-50 dark:bg-gray-700 rounded-lg")
+                            div(class="text-xl font-bold text-gray-800 dark:text-white mb-1") {{ event.duration }}
+                            div(class="text-sm text-gray-600 dark:text-gray-400") Продолжительность
+                        
+                        div(class="text-center p-4 bg-gray-50 dark:bg-gray-700 rounded-lg")
+                            div(class="text-xl font-bold text-gray-800 dark:text-white mb-1") {{ event.ageRestriction }}
+                            div(class="text-sm text-gray-600 dark:text-gray-400") Возраст
+                    
+                    div(class="prose prose-lg dark:prose-invert max-w-none mb-8")
+                        p(class="text-gray-700 dark:text-gray-300 leading-relaxed") {{ event.description }}
+                    
+                    div(class="flex flex-col sm:flex-row gap-4")
+                        button(
+                            @click="bookTickets"
+                            class="bg-accent text-white px-8 py-4 rounded-lg font-medium hover:bg-yellow-600 transition-colors flex items-center justify-center"
+                        )
+                            svg(class="w-5 h-5 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24")
+                                path(stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M16 11V7a4 4 0 00-8 0v4M5 9h14l1 12H4L5 9z")
+                            span Купить билеты
+                        
+                        button(
+                            @click="$router.back()"
+                            class="border border-gray-300 text-gray-700 px-6 py-4 rounded-lg font-medium hover:bg-gray-50 transition-colors dark:border-gray-600 dark:text-gray-300 dark:hover:bg-gray-700"
+                        ) Назад к мероприятиям
+        
+        div(v-else class="text-center")
+            div(class="empty-icon mx-auto mb-4")
+                svg(class="w-24 h-24 text-gray-400 mx-auto" fill="none" stroke="currentColor" viewBox="0 0 24 24")
+                    path(stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9.172 16.172a4 4 0 015.656 0M9 10h.01M15 10h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z")
+            h3(class="text-xl font-bold text-gray-600 dark:text-gray-400 mb-2") Мероприятие не найдено
+            p(class="text-gray-500 dark:text-gray-500 mb-6") Запрошенное мероприятие не существует или было удалено
+            app-link(
+                to="/events"
+                class="bg-accent text-white px-6 py-2 rounded-lg hover:bg-yellow-600 transition-colors inline-block"
+            ) Вернуться к мероприятиям

+ 0 - 0
vue/app/pages/EventDetail/index.styl


+ 110 - 229
vue/app/pages/Events/index.coffee

@@ -1,243 +1,124 @@
-# app/pages/Events/index.coffee
-document.head.insertAdjacentHTML('beforeend','<style type="text/tailwindcss"  page="Events">'+stylFns['app/pages/Events/index.styl']+'</style>')
+document.head.insertAdjacentHTML('beforeend','<style type="text/tailwindcss">'+stylFns['app/pages/Events/index.styl']+'</style>')
 
 module.exports =
-  name: 'Events'
-  render: (new Function '_ctx', '_cache', renderFns['app/pages/Events/index.pug'])()
-  data: ->
-    allEvents: [
-      {
-        id: 1
-        title: 'Концерт симфонического оркестра'
-        date: '2025-10-15'
-        time: '19:00'
-        description: 'Произведения Чайковского и Рахманинова в исполнении Национального симфонического оркестра под руководством маэстро Фирдавса Абдурахмонова. Незабываемый вечер классической музыки в акустике мирового уровня.'
-        image: 'https://avatars.mds.yandex.net/get-altay/9822373/2a0000019377c5d52c95d3175340aab4a35a/XXL_height'
-        category: 'classical'
-        price: 50
-        venue: 'Большой зал'
-        duration: '2 часа 30 минут'
-        ageRestriction: '12+'
-        availableTickets: 45
-      }
-      {
-        id: 2
-        title: 'Вечер таджикской народной музыки'
-        date: '2025-10-20'
-        time: '18:30'
-        description: 'Выступление фольклорного ансамбля "Шашмаком" с программой традиционных мелодий и танцев. Погрузитесь в богатую культурную традицию Таджикистана.'
-        image: 'https://avatars.mds.yandex.net/get-altay/9822373/2a0000019377c5d52c95d3175340aab4a35a/XXL_height'
-        category: 'folk'
-        price: 30
-        venue: 'Малый зал'
-        duration: '2 часа'
-        ageRestriction: '6+'
-        availableTickets: 28
-      }
-      {
-        id: 3
-        title: 'Джазовый фестиваль "Borbad Jazz"'
-        date: '2025-10-25'
-        time: '20:00'
-        description: 'Международные джазовые коллективы из Европы и Азии в уникальной акустике зала. Три дня незабываемой музыки от лучших джазменов мира.'
-        image: 'https://avatars.mds.yandex.net/get-altay/9822373/2a0000019377c5d52c95d3175340aab4a35a/XXL_height'
-        category: 'jazz'
-        price: 70
-        venue: 'Большой зал'
-        duration: '3 часа'
-        ageRestriction: '16+'
-        availableTickets: 15
-      }
-      {
-        id: 4
-        title: 'Концерт популярной музыки'
-        date: '2025-10-28'
-        time: '19:30'
-        description: 'Лучшие поп-исполнители Таджикистана представят новые хиты и классические композиции. Энергичное шоу с современной хореографией.'
-        image: 'https://avatars.mds.yandex.net/get-altay/9822373/2a0000019377c5d52c95d3175340aab4a35a/XXL_height'
-        category: 'pop'
-        price: 45
-        venue: 'Большой зал'
-        duration: '2 часа 15 минут'
-        ageRestriction: '12+'
-        availableTickets: 67
-      }
-      {
-        id: 5
-        title: 'Оперный гала-концерт'
-        date: '2025-11-02'
-        time: '18:00'
-        description: 'Известные оперные певцы исполнят арии из мировых шедевров оперного искусства. Вечер великой музыки в исполнении мастеров.'
-        image: 'https://avatars.mds.yandex.net/get-altay/9822373/2a0000019377c5d52c95d3175340aab4a35a/XXL_height'
-        category: 'classical'
-        price: 80
-        venue: 'Большой зал'
-        duration: '2 часа 45 минут'
-        ageRestriction: '12+'
-        availableTickets: 23
-      }
-      {
-        id: 6
-        title: 'Танцевальное шоу "Восточные ритмы"'
-        date: '2025-11-05'
-        time: '19:00'
-        description: 'Традиционные и современные танцевальные коллективы представят красочное шоу. Яркие костюмы, зажигательная музыка и мастерство танцовщиков.'
-        image: 'https://avatars.mds.yandex.net/get-altay/9822373/2a0000019377c5d52c95d3175340aab4a35a/XXL_height'
-        category: 'dance'
-        price: 35
-        venue: 'Большой зал'
-        duration: '2 часа 30 минут'
-        ageRestriction: '6+'
-        availableTickets: 89
-      }
-      {
-        id: 7
-        title: 'Камерная музыка: Струнный квартет'
-        date: '2025-11-08'
-        time: '17:00'
-        description: 'Изысканная программа камерной музыки в исполнении ведущего струнного квартета страны. Интимная атмосфера и тонкое звучание.'
-        image: 'https://avatars.mds.yandex.net/get-altay/9822373/2a0000019377c5d52c95d3175340aab4a35a/XXL_height'
-        category: 'classical'
-        price: 40
-        venue: 'Камерный зал'
-        duration: '1 час 45 минут'
-        ageRestriction: '12+'
-        availableTickets: 34
-      }
-      {
-        id: 8
-        title: 'Фестиваль современной музыки'
-        date: '2025-11-12'
-        time: '20:30'
-        description: 'Экспериментальные проекты и инновационные музыкальные направления. Откройте для себя новые грани музыкального искусства.'
-        image: 'https://avatars.mds.yandex.net/get-altay/9822373/2a0000019377c5d52c95d3175340aab4a35a/XXL_height'
-        category: 'experimental'
-        price: 55
-        venue: 'Малый зал'
-        duration: '3 часа'
-        ageRestriction: '18+'
-        availableTickets: 18
-      }
-    ]
-    displayedEvents: []
-    selectedEvent: null
-    showEventModal: false
-    loading: false
-    currentPage: 1
-    pageSize: 6
-    eventFilters: [
-      {
-        key: 'category'
-        label: 'Категория'
-        type: 'select'
-        options: [
-          { value: 'all', label: 'Все категории' }
-          { value: 'classical', label: 'Классическая музыка' }
-          { value: 'folk', label: 'Фольклор' }
-          { value: 'jazz', label: 'Джаз' }
-          { value: 'pop', label: 'Поп-музыка' }
-          { value: 'dance', label: 'Танцевальное шоу' }
-          { value: 'experimental', label: 'Экспериментальная музыка' }
+    name: 'Events'
+    render: (new Function '_ctx', '_cache', renderFns['app/pages/Events/index.pug'])()
+    data: ->
+        allEvents: []
+        displayedEvents: []
+        loading: false
+        currentPage: 1
+        pageSize: 9
+        eventFilters: [
+            {
+                key: 'category'
+                label: 'Категория'
+                type: 'select'
+                options: [
+                    { value: 'all', label: 'Все категории' }
+                    { value: 'classical', label: 'Классическая музыка' }
+                    { value: 'folk', label: 'Фольклор' }
+                    { value: 'jazz', label: 'Джаз' }
+                    { value: 'pop', label: 'Поп-музыка' }
+                    { value: 'dance', label: 'Танцевальное шоу' }
+                ]
+            }
+            {
+                key: 'venue'
+                label: 'Зал'
+                type: 'select'
+                options: [
+                    { value: 'all', label: 'Все залы' }
+                    { value: 'Большой зал', label: 'Большой зал' }
+                    { value: 'Малый зал', label: 'Малый зал' }
+                    { value: 'Камерный зал', label: 'Камерный зал' }
+                ]
+            }
+            {
+                key: 'priceRange'
+                label: 'Цена'
+                type: 'range'
+                options: [
+                    { value: 'all', label: 'Любая цена' }
+                    { value: 'budget', label: 'До 50 сомони' }
+                    { value: 'medium', label: '50-100 сомони' }
+                    { value: 'premium', label: 'Выше 100 сомони' }
+                ]
+            }
         ]
-      }
-      {
-        key: 'venue'
-        label: 'Зал'
-        type: 'select'
-        options: [
-          { value: 'all', label: 'Все залы' }
-          { value: 'Большой зал', label: 'Большой зал' }
-          { value: 'Малый зал', label: 'Малый зал' }
-          { value: 'Камерный зал', label: 'Камерный зал' }
+        sortOptions: [
+            { value: 'date-asc', label: 'По дате (сначала ближайшие)' }
+            { value: 'date-desc', label: 'По дате (сначала дальние)' }
+            { value: 'price-asc', label: 'По цене (сначала дешевые)' }
+            { value: 'price-desc', label: 'По цене (сначала дорогие)' }
+            { value: 'name-asc', label: 'По названию (А-Я)' }
+            { value: 'name-desc', label: 'По названию (Я-А)' }
         ]
-      }
-      {
-        key: 'priceRange'
-        label: 'Цена, сомони'
-        type: 'range'
-        min: 0
-        max: 100
-        step: 5
-      }
-    ]
-    sortOptions: [
-      { value: 'date-asc', label: 'По дате (сначала ближайшие)' }
-      { value: 'date-desc', label: 'По дате (сначала дальние)' }
-      { value: 'price-asc', label: 'По цене (сначала дешевые)' }
-      { value: 'price-desc', label: 'По цене (сначала дорогие)' }
-      { value: 'name-asc', label: 'По названию (А-Я)' }
-      { value: 'name-desc', label: 'По названию (Я-А)' }
-      { value: 'popularity', label: 'По популярности' }
-    ]
-    categoryLabels:
-      classical: 'Классика'
-      folk: 'Фольклор'
-      jazz: 'Джаз'
-      pop: 'Поп'
-      dance: 'Танцы'
-      experimental: 'Эксперимент'
+        categoryLabels:
+            classical: 'Классика'
+            folk: 'Фольклор'
+            jazz: 'Джаз'
+            pop: 'Поп'
+            dance: 'Танцы'
 
-  computed:
-    hasMoreEvents: ->
-      @currentPage * @pageSize < @allEvents.length
+    computed:
+        hasMoreEvents: ->
+            @currentPage * @pageSize < @allEvents.length
 
-  mounted: ->
-    @displayedEvents = @allEvents.slice(0, @pageSize)
+    mounted: ->
+        @loadEvents()
+        @displayedEvents = @allEvents.slice(0, @pageSize)
 
-  methods:
-    handleFilterChange: (filteredItems) ->
-      @displayedEvents = filteredItems
-      @currentPage = 1
+    methods:
+        loadEvents: ->
+            @loading = true
+            @allEvents = _.getEvents() || []
+            
+            if @allEvents.length == 0
+                setTimeout =>
+                    @loadEvents()
+                , 500
+            else
+                @loading = false
 
-    # Стало (правильно)
-    showEventDetails: (event) ->
-        openModal('EventDetailModal', { event: event })
-    
-    handleTicketBooking: (event) ->
-        debug.log "Обработка покупки билета на: "+event.title
-        openModal('SuccessModal', {
-            title: 'Билет забронирован!'
-            content: "Вы успешно забронировали билет на \""+event.title+"\". Подробности отправлены на вашу почту."
-        })
+        handleFilterChange: (filteredItems) ->
+            @displayedEvents = filteredItems
+            @currentPage = 1
 
-    bookTicket: (event) ->
-      # Открываем модальное окно покупки билетов
-      @selectedEvent = event
-      @showEventModal = true
-      # Можно также сразу перейти к покупке
-      debug.log 'Бронирование билета на:'+ event.title
+        openEventModal: (event) ->
+            _.openModal('EventDetailModal', { event: event })
 
+        loadMoreEvents: ->
+            @loading = true
+            setTimeout =>
+                @currentPage += 1
+                startIndex = 0
+                endIndex = @currentPage * @pageSize
+                @displayedEvents = @allEvents.slice(startIndex, endIndex)
+                @loading = false
+            , 1000
 
-    loadMoreEvents: ->
-      @loading = true
-      # Имитация загрузки
-      setTimeout =>
-        @currentPage += 1
-        startIndex = 0
-        endIndex = @currentPage * @pageSize
-        @displayedEvents = @allEvents.slice(startIndex, endIndex)
-        @loading = false
-      , 1000
+        resetFilters: ->
+            @displayedEvents = @allEvents.slice(0, @pageSize)
+            @currentPage = 1
 
-    resetFilters: ->
-      @displayedEvents = @allEvents.slice(0, @pageSize)
-      @currentPage = 1
+        getCategoryLabel: (category) ->
+            @categoryLabels[category] || category
 
-    getCategoryLabel: (category) ->
-      @categoryLabels[category] || category
+        getCategoryBadgeClass: (category) ->
+            classes =
+                classical: 'bg-blue-500'
+                folk: 'bg-green-500'
+                jazz: 'bg-purple-500'
+                pop: 'bg-pink-500'
+                dance: 'bg-orange-500'
+            
+            classes[category] || 'bg-gray-500'
 
-    getCategoryBadgeClass: (category) ->
-      classes =
-        classical: 'bg-blue-500'
-        folk: 'bg-green-500'
-        jazz: 'bg-purple-500'
-        pop: 'bg-pink-500'
-        dance: 'bg-orange-500'
-        experimental: 'bg-indigo-500'
-      
-      classes[category] || 'bg-gray-500'
-
-    formatDate: (dateString) ->
-      date = new Date(dateString)
-      options = { day: 'numeric', month: 'short' }
-      date.toLocaleDateString('ru-RU', options)
+        formatDate: (dateString) ->
+            try
+                date = new Date(dateString)
+                options = { day: 'numeric', month: 'short' }
+                date.toLocaleDateString('ru-RU', options)
+            catch
+                dateString

+ 7 - 15
vue/app/pages/Events/index.pug

@@ -1,7 +1,7 @@
 section(class="min-h-screen bg-gray-50 dark:bg-gray-900 py-8")
     div(class="container mx-auto px-4")
         div(class="text-center mb-12")
-            h1(class="text-4xl md:text-5xl font-bold text-gray-800 dark:text-white mb-4") Мероприятия
+            h1(class="text-4xl md:text-5xl font-bold text-gray-800 dark:text-white mb-4") Все мероприятия
             p(class="text-xl text-gray-600 dark:text-gray-400 max-w-3xl mx-auto") Откройте для себя богатую палитру культурных событий в концертном зале "Кохи Борбад"
         
         div(class="mb-8")
@@ -14,19 +14,19 @@ section(class="min-h-screen bg-gray-50 dark:bg-gray-900 py-8")
             )
         
         div(class="events-grid")
-            transition-group(name="events-list" tag="div")
+            div(class="grid grid-cols-1 md:grid-cols-2 xl:grid-cols-3 gap-6")
                 div(
                     v-for="event in displayedEvents"
                     :key="event._id"
                     class="event-card bg-white dark:bg-gray-800 rounded-xl shadow-lg overflow-hidden hover:shadow-2xl transition-all duration-300 cursor-pointer transform hover:-translate-y-2 animate-fade-in-up"
                     :class="'animation-delay-' + (($index % 6) * 100)"
-                    @click="showEventDetails(event)"
+                    @click="$router.push('/events/' + event._id)"
                 )
                     div(class="event-image relative overflow-hidden")
                         img(
                             :src="event.image"
                             :alt="event.title"
-                            class="w-full h-48 md:h-56 object-cover transition-transform duration-500 hover:scale-105"
+                            class="w-full h-48 object-cover transition-transform duration-500 hover:scale-105"
                         )
                         div(class="event-badge absolute top-4 right-4")
                             span(
@@ -38,7 +38,7 @@ section(class="min-h-screen bg-gray-50 dark:bg-gray-900 py-8")
                         
                     div(class="event-content p-6")
                         h3(class="text-xl font-bold text-gray-800 dark:text-white mb-3 line-clamp-2") {{ event.title }}
-                        p(class="text-gray-600 dark:text-gray-400 text-sm mb-4 line-clamp-3") {{ event.description }}
+                        p(class="text-gray-600 dark:text-gray-400 text-sm mb-4 line-clamp-3") {{ event.shortDescription || event.description }}
                         
                         div(class="event-meta flex items-center justify-between mb-4")
                             div(class="event-time flex items-center text-sm text-gray-500 dark:text-gray-400")
@@ -56,8 +56,8 @@ section(class="min-h-screen bg-gray-50 dark:bg-gray-900 py-8")
                                 span(class="text-sm font-normal text-gray-500 dark:text-gray-400")  сомони
                             button(
                                 class="bg-primary text-white px-4 py-2 rounded-lg hover:bg-gray-700 transition-colors text-sm font-medium"
-                                @click.stop="bookTicket(event)"
-                            ) Купить билет
+                                @click.stop="openEventModal(event)"
+                            ) Быстрый просмотр
         
         div(class="load-more text-center mt-12" v-if="hasMoreEvents && !loading")
             button(
@@ -79,11 +79,3 @@ section(class="min-h-screen bg-gray-50 dark:bg-gray-900 py-8")
                 @click="resetFilters"
                 class="bg-accent text-white px-6 py-2 rounded-lg hover:bg-yellow-600 transition-colors"
             ) Сбросить фильтры
-
-    EventDetailModal(
-        v-if="selectedEvent"
-        :isVisible="showEventModal"
-        :event="selectedEvent"
-        @update:isVisible="showEventModal = false"
-        @ticket-booking="handleTicketBooking"
-    )

+ 27 - 110
vue/app/pages/Home/index.coffee

@@ -1,129 +1,46 @@
-# app/pages/Home/index.coffee
-document.head.insertAdjacentHTML('beforeend','<style type="text/tailwindcss"  page="Home">'+stylFns['app/pages/Home/index.styl']+'</style>')
+document.head.insertAdjacentHTML('beforeend','<style type="text/tailwindcss">'+stylFns['app/pages/Home/index.styl']+'</style>')
 
 module.exports =
-  name: 'Home'
-  render: (new Function '_ctx', '_cache', renderFns['app/pages/Home/index.pug'])()
-  data: ->
-    heroSlides: [
-      {
-        id: 1,
-        image: 'https://avesta.tj/wp-content/uploads/2018/03/10-22.jpg',
-        title: 'Классическая музыка',
-        description: 'Вечер симфонической музыки',
-        cta: 'Купить билеты'
-      },
-      {
-        id: 2, 
-        image: 'https://asiaplustj.info/sites/default/files/articles/176582/borbad.jpg',
-        title: 'Фольклорные ансамбли',
-        description: 'Традиционная музыка Таджикистана',
-        cta: 'Подробнее'
-      },
-      {
-        id: 3,
-        image: 'https://asiaplustj.info/sites/default/files/articles/211528/vdushanbeprosheltadzhiksko-indiyskiyselskohozyaystvennyyforum.jpg',
-        title: 'Современные исполнители',
-        description: 'Лучшие артисты страны',
-        cta: 'Узнать расписание'
-      }
-    ]
-    events: [
-      {
-        id: 1,
-        title: 'ШОМИ ДӮСТӢ»-И ТОҶИКИСТОНУ ҚАЗОҚИСТОН',
-        date: '18 апрель 2022',
-        description: 'Асосгузори сулҳу ваҳдати миллӣ — Пешвои миллат, Президенти Ҷумҳурии Тоҷикистон муҳтарам Эмомалӣ Раҳмон ва Раиси Маҷлиси миллии Маҷлиси Олии Ҷумҳурии Тоҷикистон, Раиси шаҳри Душанбе муҳтарам Рустам Эмомалӣ Президенти Ҷумҳурии Қазоқистон муҳтарам Қосим-Жомарт Токаев дар барномаи консертии «Шоми дӯстӣ» дар Маҷмааи давлатии «Кохи Борбад», ки дар доираи Рӯзҳои фарҳанги Қазоқистон баргузор мегардад, ҳузур доранд.',
-        image: 'https://borbad.s5l.ru/assets/borbad.s5l.ru/001.jpg',
-        category: '',
-        price: ''
-      },
-      {
-        id: 2,
-        title: 'ШОМИ ДӮСТӢ»-И ТОҶИКИСТОНУ ҚИРҒИЗИСТОН',
-        date: '22 августа 2024', 
-        description: "Шоми 8 июл дар доираи сафари давлатии Президенти Қирғизистон ба Тоҷикистон дар толори бошукуҳи “Кохи Борбад” барномаи фарҳангии арбобони санъати Тоҷикистону Қирғизистон бо номи “Дӯстии абадӣ” баргузор гардид. Дар чорабинии фарҳангӣ Президенти мамлакат Эмомалӣ Раҳмон ва Президенти Қирғизистон Садир Жапаров иштирок намуданд.",
-        image: 'https://borbad.s5l.ru/assets/borbad.s5l.ru/002.jpg',
-        category: '',
-        price: ''
-      },
-      {
-        id: 3,
-        title: 'ШОМИ ДӮСТӢ»-И ТОҶИКИСТОНУ ӮЗБЕКИСТОН',
-        date: '08 июля 2025',
-        description: '10 июн Президенти Ҷумҳурии Тоҷикистон муҳтарам Эмомалӣ Раҳмон бо Президенти Ҷумҳурии Ӯзбекистон муҳтарам Шавкат Мирзиёев дар консерти тантанавии ходимони фарҳангу санъати Тоҷикистон ва Ӯзбекистон таҳти унвони “Шоми дӯстӣ”, ки дар Кохи Борбад баргузор шуд, иштирок намуданд.',
-        image: 'https://borbad.s5l.ru/assets/borbad.s5l.ru/003.jpg',
-        category: '', 
-        price: ''
-      }
-    ]
-    eventFilters: [
-      { key: 'category', label: 'Категория', type: 'select', options: [
-        { value: 'all', label: 'Все' },
-        { value: 'classical', label: 'Классическая' },
-        { value: 'folk', label: 'Фольклор' },
-        { value: 'jazz', label: 'Джаз' },
-        { value: 'pop', label: 'Поп-музыка' }
-      ]}
-    ]
-    eventSortOptions: [
-      { value: 'date-asc',   label: 'По дате (сначала ближайшие)' },
-      { value: 'date-desc',  label: 'По дате (сначала дальние)' },
-      { value: 'price-asc',  label: 'По цене (сначала дешевые)' },
-      { value: 'price-desc', label: 'По цене (сначала дорогие)' }
-    ]
-    filteredEvents: []
-  mounted: ->
-        #await this.loadData()
-        this.filteredEvents = [...this.events]
-
-  methods:
+    name: 'Home'
+    render: (new Function '_ctx', '_cache', renderFns['app/pages/Home/index.pug'])()
+    data: ->
+        heroSlides: []
+        featuredEvents: []
+    mounted: ->
+        @loadData()
+    methods:
         loadData: ->
-            # Получаем данные из глобального состояния
-            this.heroSlides = _.getSliderEvents() || []
-            this.events = _.getFeaturedEvents() || []
+            @heroSlides = _.getSliderEvents() || []
+            @featuredEvents = _.getFeaturedEvents() || []
             
-            # Если данных нет, ждем загрузки
-            if this.heroSlides.length == 0 || this.events.length == 0
+            if @heroSlides.length == 0 || @featuredEvents.length == 0
                 setTimeout =>
-                    this.loadData()
+                    @loadData()
                 , 100
 
-        handleFilterChange: (filteredItems) ->
-            this.filteredEvents = filteredItems
-
-        handleSortChange: (sortedItems) ->
-            this.filteredEvents = sortedItems
-
         openEventModal: (event) ->
-            debug.log event
-            openModal('EventDetailModal', { event: event })
+            _.openModal('EventDetailModal', { event: event })
 
         handleSlideClick: (slide) ->
             if slide.category
-                this.filteredEvents = this.events.filter((event) -> event.category == slide.category)
-                this.scrollToEvents()
+                _.openModal('SuccessModal', {
+                    title: slide.title
+                    content: slide.description
+                })
             else
-                this.$router.push('/events')
-
-        scrollToEvents: ->
-            document.querySelector('.event-card')?.scrollIntoView({
-                behavior: 'smooth',
-                block: 'start'
-            })
+                @$router.push('/events')
 
         handleSubscription: (formData) ->
             debug.log "Подписка оформлена: "+JSON.stringify(formData)
-            openModal('SuccessModal', {
+            _.openModal('SuccessModal', {
                 title: 'Подписка оформлена!'
                 content: 'Вы успешно подписались на рассылку анонсов мероприятий.'
             })
 
-        preloadHeroImages: ->
-            this.heroSlides.forEach (slide) ->
-                img = new Image()
-                img.src = slide.image
-  components:
-      'imageslider': require 'app/shared/ImageSlider'
-      'formvalidator': require 'app/shared/FormValidator'
-      'filtersort': require 'app/shared/FilterSort'
+        formatDate: (dateString) ->
+            try
+                date = new Date(dateString)
+                options = { day: 'numeric', month: 'short' }
+                date.toLocaleDateString('ru-RU', options)
+            catch
+                dateString

+ 13 - 20
vue/app/pages/Home/index.pug

@@ -1,5 +1,5 @@
 section
-    div(class="hero-section mb-16  pt-16")
+    div(class="hero-section mb-16")
         ImageSlider(
             :slides="heroSlides"
             :autoplay="true"
@@ -10,21 +10,22 @@ section
     section(class="py-16 bg-white dark:bg-gray-800")
         div(class="container mx-auto px-4")
             h2(class="text-3xl font-bold text-center mb-12 text-gray-800 dark:text-white") Ближайшие мероприятия
-            //FilterSort( :items="events" :filters="eventFilters" :sortOptions="eventSortOptions" @filter-changed="handleFilterChange" @sort-changed="handleSortChange")
-            div(class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-8 mt-8")
+            div(class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-8")
                 div(
-                    v-for="event in filteredEvents"
+                    v-for="event in featuredEvents"
                     :key="event._id"
                     class="event-card bg-gray-50 dark:bg-gray-700 rounded-xl shadow-md overflow-hidden card-hover cursor-pointer animate-fade-in-up"
                     :class="'animation-delay-' + (($index % 6) * 100)"
                     @click="openEventModal(event)"
                 )
-                    img(:src="event.image" :alt="event.title" class="w-full h-96 object-cover")
+                    img(:src="event.image" :alt="event.title" class="w-full h-48 object-cover")
                     div(class="p-6")
                         h3(class="text-xl font-bold text-gray-800 dark:text-white mb-2") {{ event.title }}
-                        p(class="text-gray-600 dark:text-gray-300 mb-4") {{ event.date }}
-                        p(class="text-gray-700 dark:text-gray-200 line-clamp-2") {{ event.description }}
-                        button(class="mt-4 bg-accent text-white px-4 py-2 rounded-lg hover:bg-yellow-600 transition-colors") Подробнее
+                        p(class="text-gray-600 dark:text-gray-300 mb-2") {{ formatDate(event.date) }}, {{ event.time }}
+                        p(class="text-gray-700 dark:text-gray-200 line-clamp-2 mb-4") {{ event.shortDescription || event.description }}
+                        div(class="flex justify-between items-center")
+                            span(class="text-2xl font-bold text-accent") {{ event.price }} сомони
+                            button(class="bg-primary text-white px-4 py-2 rounded-lg hover:bg-gray-700 transition-colors text-sm") Подробнее
     
     section(class="py-16 bg-gray-100 dark:bg-gray-900")
         div(class="container mx-auto px-4")
@@ -33,12 +34,12 @@ section
                     h2(class="text-3xl font-bold mb-6 text-gray-800 dark:text-white") Легендарный зал "Кохи Борбад"
                     p(class="text-gray-700 dark:text-gray-300 mb-4 leading-relaxed") Концертный зал "Кохи Борбад" - одна из главных культурных площадок Душанбе, известная своей богатой историей и выдающейся акустикой.
                     p(class="text-gray-700 dark:text-gray-300 mb-6 leading-relaxed") Здесь выступают лучшие артисты Таджикистана и зарубежные звезды.
-                    button(
-                        @click="$router.push('/about')"
-                        class="bg-primary text-white px-6 py-3 rounded-lg hover:bg-gray-800 transition-colors"
+                    app-link(
+                        to="/about"
+                        class="bg-primary text-white px-6 py-3 rounded-lg hover:bg-gray-800 transition-colors inline-block"
                     ) Узнать историю
                 div(class="about-image")
-                    img(src="https://avatars.mds.yandex.net/i?id=cc968d84ec5c27e85f59f49fc0daeb8004e348f8-10243924-images-thumbs&n=13" alt="Интерьер зала" class="rounded-xl shadow-2xl")
+                    img(src="/images/hall-interior.jpg" alt="Интерьер зала" class="rounded-xl shadow-2xl")
 
     section(class="py-16 bg-accent text-white")
         div(class="container mx-auto px-4 text-center")
@@ -51,11 +52,3 @@ section
                     :fields="{ email: true }"
                     @form-submitted="handleSubscription"
                 )
-
-    EventDetailModal(
-        v-if="selectedEvent"
-        :isVisible="showEventModal"
-        :event="selectedEvent"
-        @update:isVisible="showEventModal = false"
-        @ticket-booking="handleTicketBooking"
-    )

+ 1 - 1
vue/app/shared/FilterSort/index.coffee

@@ -1,4 +1,4 @@
-document.head.insertAdjacentHTML('beforeend','<style type="text/tailwindcss">'+stylFns['app/shared/FilterSort/index.styl']+'</style>')
+document.head.insertAdjacentHTML('beforeend','<style type="text/tailwindcss" component="FilterSort">'+stylFns['app/shared/FilterSort/index.styl']+'</style>')
 
 module.exports =
   name: 'FilterSort'

+ 38 - 183
vue/app/temp.coffee

@@ -16,80 +16,14 @@ document.head.insertAdjacentHTML('beforeend','<style  type="text/tailwindcss"  p
 ## базовой стиль приложения
 document.head.insertAdjacentHTML('beforeend','<style  type="text/tailwindcss"  page="root">'+stylFns['app/temp.styl']+'</style>')
 
-# Создаем глобальную шину событий
-class AppEventBus
-    constructor: ->
-        @events = {}
-    
-    on: (event, callback) ->
-        if !@events[event]
-            @events[event] = []
-        @events[event].push(callback)
-    
-    emit: (event, data) ->
-        if @events[event]
-            for callback in @events[event]
-                try
-                    callback(data)
-                catch error
-                    debug.log "Event bus error: " + error
-    
-    off: (event, callback) ->
-        if @events[event]
-            @events[event] = @events[event].filter (cb) -> cb != callback
-
-# Создаем глобально
-globalThis.EventBus = new AppEventBus()
-
-# CouchDB сервис
-class CouchDBService
-    constructor: ->
-        @baseUrl = 'http://localhost:5984'
-        @dbName = 'kohi_borbad_events'
-        @headers = 
-            'Content-Type': 'application/json'
-            'Authorization': 'Basic ' + btoa('admin:password')
-
-    getAllEvents: ->
-        try
-            response = await fetch("#{@baseUrl}/#{@dbName}/_all_docs?include_docs=true", {
-                method: 'GET'
-                headers: @headers
-            })
-            
-            if response.ok
-                data = await response.json()
-                events = data.rows.map (row) -> row.doc
-                return events.filter (event) -> !event._id.startsWith('_design/')
-            else
-                debug.log "Ошибка получения мероприятий: "+response.statusText
-                return []
-        catch error
-            debug.log "Ошибка подключения к CouchDB: "+error
-            return []
-
-    getFeaturedEvents: ->
-        events = await @getAllEvents()
-        events
-            .filter (event) -> event.isFeatured || false
-            .slice(0, 6)
-
-    getSliderEvents: ->
-        events = await @getAllEvents()
-        events
-            .filter (event) -> event.inSlider || false
-            .map (event) ->
-                id: event._id
-                image: event.image || '/images/default-event.jpg'
-                title: event.title
-                description: event.shortDescription || event.description
-                cta: event.cta || 'Подробнее'
-                category: event.category
+# Подключаем ядро системы
+CouchDBService = require 'app/core/CouchdbClass'
 
 # Маршруты
 routes = [
     { path: '/', component: require 'app/pages/Home' }
     { path: '/events', component: require 'app/pages/Events' }
+    { path: '/events/:id', component: require 'app/pages/EventDetail' }
     { path: '/about', component: require 'app/pages/About' }
     { path: '/contacts', component: require 'app/pages/Contacts' }
 ]
@@ -107,8 +41,9 @@ app = Vue.createApp
                 loading: true
                 error: null
             modalState: 
-                currentModal: null
-                modalProps: {}
+                isVisible: false
+                component: null
+                props: {}
             couchDBService: new CouchDBService()
     beforeMount: ()->
         debug.log "start beforeMount"
@@ -125,131 +60,58 @@ app = Vue.createApp
             document.documentElement.classList.remove('dark')
         
         # Загрузка данных из CouchDB
-        await @loadEventsData()
-        
-        # Обработчик открытия модальных окон - ДОЛЖЕН БЫТЬ ЗДЕСЬ
-        EventBus.on 'open-modal', (config) =>
-            debug.log "Opening modal: "+config.component
-            @modalState.currentModal = config.component
-            @modalState.modalProps = config.props || {}
+        @loadEventsData()
     methods:
         toggleTheme: ->
             @theme = if @theme == 'light' then 'dark' else 'light'
             localStorage.setItem 'theme', @theme
             document.documentElement.classList.toggle 'dark'
-        handleTicketBooking: (event) ->
-            debug.log "Обработка покупки билета на: "+event.title
-            openModal('SuccessModal', {
-                title: 'Билет забронирован!'
-                content: "Вы успешно забронировали билет на \""+event.title+"\". Подробности отправлены на вашу почту."
-            })
+        
+        openModal: (component, props = {}) ->
+            @modalState.component = component
+            @modalState.props = props
+            @modalState.isVisible = true
+        
+        closeModal: ->
+            @modalState.isVisible = false
+            @modalState.component = null
+            @modalState.props = {}
+        
         loadEventsData: ->
             @appState.loading = true
-            try
-                [events, featuredEvents, sliderEvents] = await Promise.all([
-                    @couchDBService.getAllEvents()
-                    @couchDBService.getFeaturedEvents()
-                    @couchDBService.getSliderEvents()
-                ])
-                
+            @couchDBService.getAllEvents()
+            .then (events) =>
                 @appState.events = events
-                @appState.featuredEvents = featuredEvents
-                @appState.sliderEvents = sliderEvents
+                @appState.featuredEvents = events.filter((event) -> event.isFeatured).slice(0, 6)
+                @appState.sliderEvents = events.filter((event) -> event.inSlider).map (event) ->
+                    id: event._id
+                    image: event.image || '/images/default-event.jpg'
+                    title: event.title
+                    description: event.shortDescription || event.description
+                    cta: event.cta || 'Подробнее'
+                    category: event.category
                 @appState.error = null
-                
-            catch error
+            .catch (error) =>
                 debug.log "Ошибка загрузки данных: "+error
                 @appState.error = 'Не удалось загрузить данные мероприятий'
-                @loadTestData()
-            finally
+            .finally =>
                 @appState.loading = false
         
-        loadTestData: ->
-            # Тестовые данные на случай недоступности CouchDB
-            @appState.events = [
-                {
-                    _id: '1'
-                    title: 'Концерт симфонического оркестра'
-                    date: '2020-04-18'
-                    time: '19:00'
-                    description: 'Произведения Чайковского и Рахманинова в исполнении Национального симфонического оркестра'
-                    image: '/images/event-classical.jpg'
-                    category: 'classical'
-                    price: 50
-                    venue: 'Большой зал'
-                    duration: '2 часа 30 минут'
-                    ageRestriction: '12+'
-                    availableTickets: 45
-                    isFeatured: true
-                    inSlider: true
-                    shortDescription: 'Шедевры классической музыки'
-                    cta: 'Купить билеты'
-                }
-                {
-                    _id: '2'
-                    title: 'Вечер таджикской народной музыки'
-                    date: '2025-10-20'
-                    time: '18:30'
-                    description: 'Выступление фольклорного ансамбля "Шашмаком" с программой традиционных мелодий и танцев'
-                    image: '/images/event-folk.jpg'
-                    category: 'folk'
-                    price: 30
-                    venue: 'Малый зал'
-                    duration: '2 часа'
-                    ageRestriction: '6+'
-                    availableTickets: 28
-                    isFeatured: true
-                    inSlider: true
-                    shortDescription: 'Традиционные мелодии и танцы Таджикистана'
-                    cta: 'Узнать больше'
-                }
-                {
-                    _id: '3'
-                    title: 'Джазовый фестиваль "Borbad Jazz"'
-                    date: '2025-10-25'
-                    time: '20:00'
-                    description: 'Международные джазовые коллективы из Европы и Азии в уникальной акустике зала'
-                    image: '/images/event-jazz.jpg'
-                    category: 'jazz'
-                    price: 70
-                    venue: 'Большой зал'
-                    duration: '3 часа'
-                    ageRestriction: '16+'
-                    availableTickets: 15
-                    isFeatured: true
-                    inSlider: true
-                    shortDescription: 'Международные джазовые коллективы'
-                    cta: 'Смотреть расписание'
-                }
-            ]
-            @appState.featuredEvents = @appState.events.filter((event) -> event.isFeatured).slice(0, 6)
-            @appState.sliderEvents = @appState.events.filter((event) -> event.inSlider).map (event) ->
-                id: event._id
-                image: event.image
-                title: event.title
-                description: event.shortDescription
-                cta: event.cta
-                category: event.category
-        
         getEvents: -> @appState.events
         getFeaturedEvents: -> @appState.featuredEvents
         getSliderEvents: -> @appState.sliderEvents
         isLoading: -> @appState.loading
         hasError: -> @appState.error
-        
-        closeModal: ->
-            @modalState.currentModal = null
-            @modalState.modalProps = {}
     components:
-        'themetoggle':    require 'app/shared/ThemeToggle'
-        'multilevelmenu': require 'app/shared/MultiLevelMenu'
-        'imageslider':    require 'app/shared/ImageSlider'
-        'modalwindow':    require 'app/shared/ModalWindow'
-        'formvalidator':  require 'app/shared/FormValidator'
-        'filtersort':     require 'app/shared/FilterSort'
+        'themetoggle':      require 'app/shared/ThemeToggle'
+        'multilevelmenu':   require 'app/shared/MultiLevelMenu'
+        'imageslider':      require 'app/shared/ImageSlider'
+        'modalwindow':      require 'app/shared/ModalWindow'
+        'formvalidator':    require 'app/shared/FormValidator'
+        'filtersort':       require 'app/shared/FilterSort'
         'eventdetailmodal': require 'app/shared/EventDetailModal'
-        'successmodal':   require 'app/shared/SuccessModal'
-        'applink':   require 'app/shared/AppLink'
+        'successmodal':     require 'app/shared/SuccessModal'
+        'app-link':         require 'app/shared/AppLink'
 
 app.use(VueRouter.createRouter({
     routes: routes
@@ -261,13 +123,6 @@ app.use(VueRouter.createRouter({
             return { x: 0, y: 0 }
 }))
 
-# Глобальные функции для работы с модальными окнами
-globalThis.openModal = (component, props = {}) ->
-    EventBus.emit 'open-modal', { component, props }
-
-globalThis.closeModal = ->
-    EventBus.emit 'close-modal'
-
 # подключаем в body ОБЯЗАТЕЛЬНО!!!
 app.mount('body')