Gogs пре 3 недеља
родитељ
комит
580f9dabb7
5 измењених фајлова са 595 додато и 238 уклоњено
  1. 11 1
      README.md
  2. 48 40
      vue/app/pages/Home/index.coffee
  3. 88 51
      vue/app/pages/Home/index.pug
  4. 364 77
      vue/app/temp.coffee
  5. 84 69
      vue/app/temp.pug

+ 11 - 1
README.md

@@ -1,5 +1,6 @@
 # Текущая задача
-переработай ядро сайта, app/index* исходя их изменений в структуре данных и способе их загрузки. 
+проанализируй и доработай модуль: app/shared/AppLink
+Сейчас он не работат, его задача дать универсальную обёртку как для внутренних ссылок так и для внешних
 
 
 # файл с правилами
@@ -150,7 +151,16 @@ loadData: ->       - правильно
 при форматировании кода для отделения логических блоков используй 4 пробела ("    ")
 следи за строгим соблюдением синтаксиса используемых языков (coffeescript, pug, stylus)
 в pug не используй многострочные вычисляемые атрибуты
+```
+                :class="{
+                    'bg-blue-50 dark:bg-blue-900 text-blue-600 dark:text-blue-400': currentLanguage === language.code,
+                    'text-gray-700 dark:text-gray-300 hover:bg-gray-50 dark:hover:bg-gray-700': currentLanguage !== language.code
+                }"
+``` - не правильно
 
+```
+                :class="{'bg-blue-50 dark:bg-blue-900 text-blue-600 dark:text-blue-400': currentLanguage === language.code, 'text-gray-700 dark:text-gray-300 hover:bg-gray-50 dark:hover:bg-gray-700': currentLanguage !== language.code }"
+``` - правильно
 ## работа с консолью и текстовыми константами/переменными используй конкатенацию (СТРОГО! ВАЖНО!)
 используй для вывода в консоль debug.log
 console.log "переменная temp = #{temp}" - не правильно

+ 48 - 40
vue/app/pages/Home/index.coffee

@@ -1,46 +1,54 @@
-document.head.insertAdjacentHTML('beforeend','<style type="text/tailwindcss">'+stylFns['app/pages/Home/index.styl']+'</style>')
+# Загрузка стилей компонента
+document.head.insertAdjacentHTML('beforeend','<style type="text/tailwindcss"  page="Home">'+stylFns['app/pages/Home/index.styl']+'</style>')
 
 module.exports =
-    name: 'Home'
+    name: 'HomePage'
     render: (new Function '_ctx', '_cache', renderFns['app/pages/Home/index.pug'])()
+    
     data: ->
-        heroSlides: []
-        featuredEvents: []
-    mounted: ->
-        @loadData()
+        featuredContent: []
+        loading: true
+        error: null
+    
+    beforeMount: ->
+        @loadFeaturedContent()
+    
     methods:
-        loadData: ->
-            @heroSlides = _.getSliderEvents() || []
-            @featuredEvents = _.getFeaturedEvents() || []
-            
-            if @heroSlides.length == 0 || @featuredEvents.length == 0
-                setTimeout =>
-                    @loadData()
-                , 100
-
-        openEventModal: (event) ->
-            _.openModal('EventDetailModal', { event: event })
-
-        handleSlideClick: (slide) ->
-            if slide.category
-                _.openModal('SuccessModal', {
-                    title: slide.title
-                    content: slide.description
-                })
-            else
-                @$router.push('/events')
-
-        handleSubscription: (formData) ->
-            debug.log "Подписка оформлена: "+JSON.stringify(formData)
-            _.openModal('SuccessModal', {
-                title: 'Подписка оформлена!'
-                content: 'Вы успешно подписались на рассылку анонсов мероприятий.'
-            })
-
-        formatDate: (dateString) ->
+        loadFeaturedContent: ->
             try
-                date = new Date(dateString)
-                options = { day: 'numeric', month: 'short' }
-                date.toLocaleDateString('ru-RU', options)
-            catch
-                dateString
+                @loading = true
+                
+                # Загрузка различных типов контента
+                promises = [
+                    AppDB.getSlides(limit: 6)
+                    AppDB.getUpcomingEvents(limit: 3)
+                    AppDB.getBlogPosts(limit: 3, featured: true)
+                ]
+                
+                results = await Promise.all(promises)
+                
+                @featuredContent = [
+                    { type: 'slides', items: results[0], title: 'Главные события' }
+                    { type: 'events', items: results[1], title: 'Ближайшие мероприятия' }
+                    { type: 'blog', items: results[2], title: 'Последние новости' }
+                ]
+                
+                @loading = false
+                
+            catch error
+                @error = "Ошибка загрузки контента: "+error
+                @loading = false
+        
+        # Получение текста с учетом текущего языка
+        getText: (textArray) ->
+            return AppDB.multilingual.getText(textArray, '')
+        
+        # Форматирование даты
+        formatDate: (dateString) ->
+            return new Date(dateString).toLocaleDateString('ru-RU', {
+                year: 'numeric'
+                month: 'long'
+                day: 'numeric'
+            })
+    
+    template: renderFns['app/pages/Home/index.pug']

+ 88 - 51
vue/app/pages/Home/index.pug

@@ -1,54 +1,91 @@
-section
-    div(class="hero-section mb-16")
-        ImageSlider(
-            :slides="heroSlides"
-            :autoplay="true"
-            :duration="6000"
-            @slide-click="handleSlideClick"
-        )
-    
-    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") Ближайшие мероприятия
-            div(class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-8")
-                div(
-                    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-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-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="home-page")
+    //- Hero Slider
+    section(v-if="_.appState.slides.length > 0" class="mb-16")
+        imageslider(:slides="_.appState.slides")
+
+    //- Featured Events
+    section(v-if="_.appState.featuredEvents.length > 0" class="container mx-auto px-4 mb-16")
+        h2(class="text-3xl font-bold text-center mb-8 text-gray-800 dark:text-white") Ближайшие мероприятия
+        div(class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6")
+            div(
+                v-for="event in _.appState.featuredEvents" 
+                :key="event._id"
+                class="bg-white dark:bg-gray-800 rounded-lg shadow-md overflow-hidden hover:shadow-lg transition-shadow"
+            )
+                app-link(:to="'/events/'+event._id")
+                    img(
+                        :src="event.image" 
+                        :alt="event.title"
+                        class="w-full h-48 object-cover"
+                    )
+                    div(class="p-4")
+                        h3(class="text-xl font-semibold mb-2 text-gray-800 dark:text-white") {{ event.title }}
+                        p(class="text-gray-600 dark:text-gray-300 mb-2") {{ formatDate(event.event_data.event_date) }}
+                        p(class="text-gray-600 dark:text-gray-300 mb-4") {{ event.event_data.location }}
                         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")
-            div(class="grid grid-cols-1 lg:grid-cols-2 gap-12 items-center")
-                div(class="about-content")
-                    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") Здесь выступают лучшие артисты Таджикистана и зарубежные звезды.
-                    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="/images/hall-interior.jpg" alt="Интерьер зала" class="rounded-xl shadow-2xl")
+                            span(class="text-lg font-bold text-blue-600") {{ event.event_data.price }} TJS
+                            span(
+                                :class="{'bg-green-100 text-green-800': event.event_data.status === 'upcoming', 'bg-blue-100 text-blue-800': event.event_data.status === 'ongoing', 'bg-gray-100 text-gray-800': event.event_data.status === 'completed' }"
+                                class="px-2 py-1 rounded text-sm"
+                            ) {{ event.event_data.status === 'upcoming' ? 'Скоро' : event.event_data.status === 'ongoing' ? 'Сейчас' : 'Завершено' }}
+
+    //- Latest Blog Posts
+    section(v-if="_.appState.blogPosts.length > 0" class="container mx-auto px-4 mb-16")
+        div(class="flex justify-between items-center mb-8")
+            h2(class="text-3xl font-bold text-gray-800 dark:text-white") Последние новости
+            app-link(
+                to="/blog" 
+                class="text-blue-600 hover:text-blue-700 font-semibold transition-colors"
+            ) Все новости →
+        
+        div(class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6")
+            div(
+                v-for="post in _.appState.blogPosts" 
+                :key="post._id"
+                class="bg-white dark:bg-gray-800 rounded-lg shadow-md overflow-hidden hover:shadow-lg transition-shadow"
+            )
+                app-link(:to="'/blog/'+post._id")
+                    img(
+                        :src="post.image" 
+                        :alt="post.title"
+                        class="w-full h-48 object-cover"
+                    )
+                    div(class="p-4")
+                        h3(class="text-xl font-semibold mb-2 text-gray-800 dark:text-white") {{ post.title }}
+                        p(class="text-gray-600 dark:text-gray-300 mb-4 line-clamp-3") {{ post.excerpt }}
+                        div(class="flex justify-between items-center text-sm text-gray-500")
+                            span {{ formatDate(post.created_at) }}
+                            span {{ post.author }}
+
+    //- Categories
+    section(v-if="_.appState.categories.length > 0" class="container mx-auto px-4 mb-16")
+        h2(class="text-3xl font-bold text-center mb-8 text-gray-800 dark:text-white") Категории
+        div(class="grid grid-cols-2 md:grid-cols-4 gap-4")
+            app-link(
+                v-for="category in _.appState.categories" 
+                :key="category._id"
+                :to="'/events?category='+category._id"
+                class="bg-white dark:bg-gray-800 rounded-lg p-6 text-center shadow-md hover:shadow-lg transition-shadow"
+            )
+                div(
+                    :style="{ color: category.color }"
+                    class="text-3xl mb-2"
+                ) 
+                    | {{ category.icon }}
+                h3(class="text-lg font-semibold text-gray-800 dark:text-white mb-2") {{ category.name }}
+                p(class="text-gray-600 dark:text-gray-300 text-sm") {{ category.description }}
 
-    section(class="py-16 bg-accent text-white")
+    //- Call to Action
+    section(class="bg-blue-600 text-white py-16")
         div(class="container mx-auto px-4 text-center")
-            h2(class="text-3xl font-bold mb-4") Будьте в курсе мероприятий
-            p(class="text-xl mb-8 text-yellow-100") Подпишитесь на рассылку и получайте анонсы концертов
-            div(class="max-w-md mx-auto")
-                FormValidator(
-                    placeholder="Введите ваш email"
-                    buttonText="Подписаться"
-                    :fields="{ email: true }"
-                    @form-submitted="handleSubscription"
-                )
+            h2(class="text-3xl font-bold mb-4") Станьте частью нашего сообщества
+            p(class="text-xl mb-8 opacity-90") Присоединяйтесь к нам и откройте для себя мир искусства и культуры
+            div(class="flex flex-col sm:flex-row justify-center space-y-4 sm:space-y-0 sm:space-x-4")
+                app-link(
+                    to="/events" 
+                    class="bg-white text-blue-600 px-8 py-3 rounded-lg font-semibold hover:bg-gray-100 transition-colors"
+                ) Смотреть мероприятия
+                app-link(
+                    to="/contacts" 
+                    class="border-2 border-white text-white px-8 py-3 rounded-lg font-semibold hover:bg-white hover:text-blue-600 transition-colors"
+                ) Связаться с нами

+ 364 - 77
vue/app/temp.coffee

@@ -5,7 +5,7 @@ globalThis.stylFns   = require 'styl.json'
 # подключение мета информации (строго в данном фиде)
 document.head.insertAdjacentHTML 'beforeend','<meta charset="UTF-8">'
 document.head.insertAdjacentHTML 'beforeend','<meta name="viewport" content="width=device-width, initial-scale=1.0">'
-document.head.insertAdjacentHTML('beforeend','<title>Кохи Борбад - Концертный зал Душанбе</title>')
+document.head.insertAdjacentHTML('beforeend','<title> Кохи Борбад - Концертный зал Душанбе</title>')
 
 # Настройка tailwind
 tailwind.config = require 'tailwind.config.js'
@@ -16,114 +16,401 @@ document.head.insertAdjacentHTML('beforeend','<style  type="text/tailwindcss"  p
 ## базовой стиль приложения
 document.head.insertAdjacentHTML('beforeend','<style  type="text/tailwindcss"  page="root">'+stylFns['app/temp.styl']+'</style>')
 
-# Подключаем ядро системы
-CouchDBService = require 'app/core/CouchdbClass'
+# Создаем глобальную шину событий
+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()
+
+# Класс для работы с мультиязычными данными
+class MultilingualData
+    constructor: ->
+        @currentLanguage = 'ru'
+        @availableLanguages = ['ru', 'en', 'tj']
+        @fallbackLanguage = 'ru'
+    
+    # Установка текущего языка
+    setLanguage: (language) ->
+        if language in @availableLanguages
+            @currentLanguage = language
+            EventBus.emit('language_changed', language)
+            debug.log "Язык изменен на: "+language
+    
+    # Получение текста для текущего языка
+    getText: (textArray, fallback = '') ->
+        if not textArray or not Array.isArray(textArray)
+            return fallback
+        
+        languageIndex = @availableLanguages.indexOf(@currentLanguage)
+        if languageIndex is -1 or languageIndex >= textArray.length
+            languageIndex = @availableLanguages.indexOf(@fallbackLanguage)
+        
+        return textArray[languageIndex] or fallback
+    
+    # Получение всех текстов для отладки
+    getAllTexts: (textArray) ->
+        if not textArray or not Array.isArray(textArray)
+            return {}
+        
+        result = {}
+        for lang, index in @availableLanguages
+            if textArray[index]
+                result[lang] = textArray[index]
+        return result
+
+# Класс для работы с базой данных
+class AppDatabase
+    constructor: ->
+        @db = null
+        @baseUrl = 'https://oleg:631074@couchdb.favt.ru.net'
+        @dbName = 'borbad_events'
+        @initialized = false
+        @multilingual = new MultilingualData()
+    
+    # Инициализация базы данных
+    initialize: ->
+        try
+            PouchDB = require 'pouchdb'
+            @db = new PouchDB(@baseUrl+"/"+@dbName)
+            
+            # Проверяем соединение
+            info = await @db.info()
+            debug.log "База данных подключена: "+info.db_name
+            
+            @initialized = true
+            EventBus.emit('database_ready', @db)
+            
+        catch error
+            debug.log "Ошибка подключения к базе данных: "+error
+            EventBus.emit('database_error', error)
+    
+    # Получение документов по типу с мультиязычной обработкой
+    getDocumentsByType: (type, options = {}) ->
+        if not @initialized
+            throw new Error("База данных не инициализирована")
+        
+        try
+            viewName = options.view or 'published_by_domain_language'
+            designDoc = options.designDoc or 'multilingual_content'
+            
+            result = await @db.query(designDoc+'/'+viewName, {
+                startkey: [options.domain or 'borbad.s5l.ru', @multilingual.currentLanguage]
+                endkey: [options.domain or 'borbad.s5l.ru', @multilingual.currentLanguage, {}]
+                include_docs: true
+                descending: options.descending or true
+                limit: options.limit or 10
+                skip: options.skip or 0
+            })
+            
+            # Обрабатываем мультиязычные данные
+            processedDocs = result.rows.map (row) =>
+                @processMultilingualDocument(row.doc)
+            
+            return processedDocs
+            
+        catch error
+            debug.log "Ошибка получения документов типа "+type+": "+error
+            return []
+    
+    # Обработка мультиязычного документа
+    processMultilingualDocument: (doc) ->
+        if not doc
+            return null
+        
+        processedDoc = Object.assign({}, doc)
+        
+        # Обрабатываем мультиязычные поля
+        multilingualFields = ['title', 'content', 'excerpt', 'author', 'name', 'description', 'location']
+        
+        for field in multilingualFields
+            if doc[field] and Array.isArray(doc[field])
+                processedDoc[field] = @multilingual.getText(doc[field])
+        
+        # Обрабатываем вложенные мультиязычные структуры
+        if doc.seo
+            processedDoc.seo = {}
+            if doc.seo.description and Array.isArray(doc.seo.description)
+                processedDoc.seo.description = @multilingual.getText(doc.seo.description)
+            if doc.seo.title and Array.isArray(doc.seo.title)
+                processedDoc.seo.title = @multilingual.getText(doc.seo.title)
+            if doc.seo.keywords and Array.isArray(doc.seo.keywords)
+                processedDoc.seo.keywords = @multilingual.getText(doc.seo.keywords, [])
+        
+        # Обрабатываем специфичные данные для разных типов
+        if doc.type is 'event' and doc.event_data
+            processedDoc.event_data = Object.assign({}, doc.event_data)
+            if doc.event_data.location and Array.isArray(doc.event_data.location)
+                processedDoc.event_data.location = @multilingual.getText(doc.event_data.location)
+            if doc.event_data.price and Array.isArray(doc.event_data.price)
+                processedDoc.event_data.price = @multilingual.getText(doc.event_data.price)
+        
+        if doc.type is 'product' and doc.product_data
+            processedDoc.product_data = Object.assign({}, doc.product_data)
+            if doc.product_data.price and Array.isArray(doc.product_data.price)
+                processedDoc.product_data.price = @multilingual.getText(doc.product_data.price)
+        
+        if doc.type is 'slide' and doc.slide_data
+            processedDoc.slide_data = Object.assign({}, doc.slide_data)
+            if doc.slide_data.button_text and Array.isArray(doc.slide_data.button_text)
+                processedDoc.slide_data.button_text = @multilingual.getText(doc.slide_data.button_text)
+            if doc.slide_data.button_link and Array.isArray(doc.slide_data.button_link)
+                processedDoc.slide_data.button_link = @multilingual.getText(doc.slide_data.button_link)
+        
+        return processedDoc
+    
+    # Получение блог постов
+    getBlogPosts: (options = {}) ->
+        options.type = 'blog_post'
+        return await @getDocumentsByType('blog_post', options)
+    
+    # Получение мероприятий
+    getEvents: (options = {}) ->
+        options.type = 'event'
+        options.view = 'by_date_multilingual'
+        return await @getDocumentsByType('event', options)
+    
+    # Получение предстоящих мероприятий
+    getUpcomingEvents: (options = {}) ->
+        options.type = 'event'
+        options.view = 'upcoming_events'
+        return await @getDocumentsByType('event', options)
+    
+    # Получение товаров
+    getProducts: (options = {}) ->
+        options.type = 'product'
+        options.view = 'by_status_multilingual'
+        return await @getDocumentsByType('product', options)
+    
+    # Получение слайдов
+    getSlides: (options = {}) ->
+        options.type = 'slide'
+        options.view = 'active_ordered_multilingual'
+        options.descending = false
+        return await @getDocumentsByType('slide', options)
+    
+    # Получение категорий
+    getCategories: (options = {}) ->
+        if not @initialized
+            throw new Error("База данных не инициализирована")
+        
+        try
+            result = await @db.query('categories_hierarchical_multilingual/by_level_multilingual', {
+                startkey: [options.domain or 'borbad.s5l.ru', @multilingual.currentLanguage, 0]
+                endkey: [options.domain or 'borbad.s5l.ru', @multilingual.currentLanguage, 0, {}]
+                include_docs: true
+                ascending: true
+            })
+            
+            processedCategories = result.rows.map (row) =>
+                @processMultilingualDocument(row.doc)
+            
+            return processedCategories
+            
+        catch error
+            debug.log "Ошибка получения категорий: "+error
+            return []
+    
+    # Поиск по контенту
+    searchContent: (query, options = {}) ->
+        if not @initialized
+            throw new Error("База данных не инициализирована")
+        
+        try
+            result = await @db.query('universal_multilingual_search/content_search', {
+                startkey: [options.domain or 'borbad.s5l.ru', @multilingual.currentLanguage, query.toLowerCase()]
+                endkey: [options.domain or 'borbad.s5l.ru', @multilingual.currentLanguage, query.toLowerCase() + "\ufff0"]
+                include_docs: true
+                limit: options.limit or 20
+            })
+            
+            processedResults = result.rows.map (row) =>
+                @processMultilingualDocument(row.doc)
+            
+            return processedResults
+            
+        catch error
+            debug.log "Ошибка поиска: "+error
+            return []
+    
+    # Получение документа по ID
+    getDocumentById: (id) ->
+        if not @initialized
+            throw new Error("База данных не инициализирована")
+        
+        try
+            doc = await @db.get(id)
+            return @processMultilingualDocument(doc)
+        catch error
+            debug.log "Ошибка получения документа "+id+": "+error
+            return null
+
+# Создаем глобальные экземпляры
+globalThis.AppDB = new AppDatabase()
+globalThis.Multilingual = new MultilingualData()
 
 # Маршруты
 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' }
+  { path: '/', component: require 'app/pages/Home' }
+  { path: '/events', component: require 'app/pages/Events' }
+  { path: '/events/:id', component: require 'app/pages/EventDetail' }
+#  { path: '/blog', component: require 'app/pages/Blog' }
+#  { path: '/blog/:id', component: require 'app/pages/BlogDetail' }
+#  { path: '/products', component: require 'app/pages/Products' }
+#  { path: '/products/:id', component: require 'app/pages/ProductDetail' }
+  { path: '/about', component: require 'app/pages/About' }
+  { path: '/contacts', component: require 'app/pages/Contacts' }
 ]
 
 # Глобальное определение vuejs приложения
 app = Vue.createApp
-    name: 'app'
-    data: ()->
+  name: 'app'
+  data: ()->
         return  
-            theme: 'light'
+            dbReady: false
+            loading: true
+            error: null
+            currentLanguage: 'ru'
+            availableLanguages: ['ru', 'en', 'tj']
+            
+            # Глобальное состояние приложения
             appState:
-                events: []
+                slides: []
                 featuredEvents: []
-                sliderEvents: []
+                blogPosts: []
+                products: []
+                categories: []
                 loading: true
                 error: null
-            modalState: 
+            
+            # Состояние модальных окон
+            modalState:
                 isVisible: false
                 component: null
                 props: {}
-            couchDBService: new CouchDBService()
-    beforeMount: ()->
+  beforeMount: ()->
         debug.log "start beforeMount"
         # определение контекста vuejs приложения как глобальной переменной _
         globalThis._ = @
-    render: (new Function '_ctx', '_cache', renderFns['app/temp.pug'])()
-    mounted: ->
-        # Предзагрузка темы
-        if localStorage.theme == 'dark' || (!('theme' in localStorage) && window.matchMedia('(prefers-color-scheme: dark)').matches)
-            @theme = 'dark'
-            document.documentElement.classList.add('dark')
-        else
-            @theme = 'light'
-            document.documentElement.classList.remove('dark')
-        
-        # Загрузка данных из CouchDB
-        @loadEventsData()
-    methods:
-        toggleTheme: ->
-            @theme = if @theme == 'light' then 'dark' else 'light'
-            localStorage.setItem 'theme', @theme
-            document.documentElement.classList.toggle 'dark'
         
+        # Инициализация базы данных
+        AppDB.initialize().then =>
+            @dbReady = true
+            @loadInitialData()
+        
+        # Слушаем события смены языка
+        EventBus.on 'language_changed', (language) =>
+            @currentLanguage = language
+            @loadInitialData()
+        
+  mounted: ->
+        debug.log "App mounted"
+        
+  methods:
+        # Загрузка начальных данных
+        loadInitialData: ->
+            @appState.loading = true
+            @appState.error = null
+            
+            Promise.all([
+                @loadSlides()
+                @loadFeaturedEvents()
+                @loadBlogPosts()
+                @loadCategories()
+            ]).then =>
+                @appState.loading = false
+            .catch (error) =>
+                @appState.error = "Ошибка загрузки данных: "+error
+                @appState.loading = false
+        
+        # Загрузка слайдов
+        loadSlides: ->
+            AppDB.getSlides(limit: 6).then (slides) =>
+                @appState.slides = slides
+            .catch (error) =>
+                debug.log "Ошибка загрузки слайдов: "+error
+        
+        # Загрузка избранных мероприятий
+        loadFeaturedEvents: ->
+            AppDB.getUpcomingEvents(limit: 4).then (events) =>
+                @appState.featuredEvents = events
+            .catch (error) =>
+                debug.log "Ошибка загрузки мероприятий: "+error
+        
+        # Загрузка блог постов
+        loadBlogPosts: ->
+            AppDB.getBlogPosts(limit: 6).then (posts) =>
+                @appState.blogPosts = posts
+            .catch (error) =>
+                debug.log "Ошибка загрузки блог постов: "+error
+        
+        # Загрузка категорий
+        loadCategories: ->
+            AppDB.getCategories().then (categories) =>
+                @appState.categories = categories
+            .catch (error) =>
+                debug.log "Ошибка загрузки категорий: "+error
+        
+        # Смена языка
+        changeLanguage: (language) ->
+            if language in @availableLanguages
+                AppDB.multilingual.setLanguage(language)
+                @currentLanguage = language
+        
+        # Поиск по сайту
+        search: (query) ->
+            if query and query.length > 2
+                AppDB.searchContent(query).then (results) ->
+                    EventBus.emit('search_results', results)
+        
+        # Открытие модального окна
         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
-            @couchDBService.getAllEvents()
-            .then (events) =>
-                @appState.events = events
-                @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) =>
-                debug.log "Ошибка загрузки данных: "+error
-                @appState.error = 'Не удалось загрузить данные мероприятий'
-            .finally =>
-                @appState.loading = false
-        
-        getEvents: -> @appState.events
-        getFeaturedEvents: -> @appState.featuredEvents
-        getSliderEvents: -> @appState.sliderEvents
-        isLoading: -> @appState.loading
-        hasError: -> @appState.error
-    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'
-        'eventdetailmodal': require 'app/shared/EventDetailModal'
-        'successmodal':     require 'app/shared/SuccessModal'
-        'app-link':         require 'app/shared/AppLink'
+  render: (new Function '_ctx', '_cache', renderFns['app/temp.pug'])()
+  
+  components:
+      'themetoggle':    require 'app/shared/ThemeToggle'
+      'multilevelmenu': require 'app/shared/MultiLevelMenu'
+      'imageslider':    require 'app/shared/ImageSlider'
+      'app-link':       require 'app/shared/AppLink'
+      'language-switcher': require 'app/shared/LanguageSwitcher'
 
 app.use(VueRouter.createRouter({
-    routes: routes
-    history: VueRouter.createWebHistory()
-    scrollBehavior: (to, from, savedPosition) ->
-        if savedPosition
-            return savedPosition
-        else
-            return { x: 0, y: 0 }
+  routes: routes
+  history: VueRouter.createWebHistory()
+  scrollBehavior: (to, from, savedPosition) ->
+    if savedPosition
+      return savedPosition
+    else
+      return { x: 0, y: 0 }
 }))
 
 # подключаем в body ОБЯЗАТЕЛЬНО!!!
 app.mount('body')
-
-debug.log "Vue application initialized successfully"

+ 84 - 69
vue/app/temp.pug

@@ -1,76 +1,91 @@
-div(id="app" class="min-h-full bg-gray-50 dark:bg-gray-900 transition-colors duration-300")
+div(id="app" class="min-h-screen bg-white dark:bg-gray-900 transition-colors duration-300")
+    //- Header
+    header(class="bg-white dark:bg-gray-800 shadow-sm sticky top-0 z-50")
+        div(class="container mx-auto px-4")
+            div(class="flex justify-between items-center py-4")
+                //- Logo
+                div(class="flex items-center space-x-4")
+                    router-link(to="/" class="text-2xl font-bold text-gray-800 dark:text-white")
+                        | Кохи Борбад
+                    
 
-    div(class="transition-all duration-300")
-                header(class="bg-primary text-white shadow-lg")
-                    div(class="container mx-auto px-4 py-4")
-                        div(class="flex justify-between items-center")
-                            div
-                                router-link(to="/" class="text-2xl font-bold text-accent") Кохи Борбад
-                            div(class="flex items-center space-x-4")
-                                MultiLevelMenu
-                                ThemeToggle
 
-                main
-                    router-view(v-slot="{ Component }")
-                        transition(name="page-slide" mode="out-in")
-                            component(:is="Component")
+                //- Navigation
+                nav(class="hidden md:flex space-x-8")
+                    app-link(to="/" class="text-gray-600 dark:text-gray-300 hover:text-gray-900 dark:hover:text-white transition-colors") Главная
+                    app-link(to="/events" class="text-gray-600 dark:text-gray-300 hover:text-gray-900 dark:hover:text-white transition-colors") Мероприятия
+                    app-link(to="/blog" class="text-gray-600 dark:text-gray-300 hover:text-gray-900 dark:hover:text-white transition-colors") Блог
+                    app-link(to="/products" class="text-gray-600 dark:text-gray-300 hover:text-gray-900 dark:hover:text-white transition-colors") Магазин
+                    app-link(to="/about" class="text-gray-600 dark:text-gray-300 hover:text-gray-900 dark:hover:text-white transition-colors") О нас
+                    app-link(to="/contacts" class="text-gray-600 dark:text-gray-300 hover:text-gray-900 dark:hover:text-white transition-colors") Контакты
 
-                footer(class="bg-primary text-white py-8 mt-12")
-                    div(class="container mx-auto px-4")
-                        div(class="grid grid-cols-1 md:grid-cols-3 gap-8")
-                            div(class="footer-section")
-                                h3(class="text-xl font-bold text-accent mb-4") Контакты
-                                p пр. И. Сомони, 26, Душанбе
-                                p Телефон: +992 372 27 09 46
-                                p Email: info@borbad.tj
-                                br
-                                p
-                                    img(src="https://borbad.s5l.ru/assets/borbad.s5l.ru/000.png")
-                            
-                            div(class="footer-section")
-                                h3(class="text-xl font-bold text-accent mb-4") Быстрые ссылки
-                                div(class="flex flex-col space-y-2")
-                                    router-link(to="/events" class="hover:text-accent transition-colors") Мероприятия
-                                    router-link(to="/about" class="hover:text-accent transition-colors") О зале
-                                    router-link(to="/contacts" class="hover:text-accent transition-colors") Контакты
-                                    
-                            
-                            div(class="footer-section")
-                                h3(class="text-xl font-bold text-accent mb-4") Подписка
-                                p Подпишитесь на новости о мероприятиях
-                                FormValidator(placeholder="Ваш email" buttonText="Подписаться")
+                //- Theme Toggle and Mobile Menu
+                div(class="flex items-center space-x-4")
+                    
+                    multilevelmenu
+                    //- Language Switcher
+                    language-switcher
+                    themetoggle
 
+    //- Main Content
+    main(class="flex-grow")
+        //- Loading State
+        div(v-if="appState.loading" class="container mx-auto px-4 py-8")
+            div(class="flex justify-center items-center py-12")
+                div(class="animate-spin rounded-full h-12 w-12 border-b-2 border-blue-600")
+        
+        //- Error State
+        div(v-else-if="appState.error" class="container mx-auto px-4 py-8")
+            div(class="bg-red-100 border border-red-400 text-red-700 px-4 py-3 rounded")
+                p {{ appState.error }}
+        
+        //- Router View
+        div(v-else)
+            router-view
 
-//- Глобальные модальные окна - ДОЛЖНЫ БЫТЬ ЗДЕСЬ
-div(class="ModalContainer") {{ modalState.currentModal }} - {{ modalState.currentModal == 'EventDetailModal' }}
-    ModalWindow(
-        v-if="modalState.currentModal == 'EventDetailModal'"
-        :isVisible="true"
-        @update:isVisible="closeModal"
-        :title="modalState.modalProps.event?.title || 'Мероприятие'"
-        :contentClass="'max-w-4xl'"
-        :showFooter="false"
-    )
-        template([body])
-            EventDetailModal(
-                :event="modalState.modalProps.event"
-                :isVisible="true"
-                @update:isVisible="closeModal"
-                @ticket-booking="handleTicketBooking"
-            )
+    //- Footer
+    footer(class="bg-gray-800 text-white mt-16")
+        div(class="container mx-auto px-4 py-8")
+            div(class="grid grid-cols-1 md:grid-cols-4 gap-8")
+                //- About
+                div
+                    h3(class="text-lg font-semibold mb-4") О нас
+                    p(class="text-gray-300") Концертный зал Борбад - культурный центр Душанбе, место встречи искусства и культуры.
+                
+                //- Quick Links
+                div
+                    h3(class="text-lg font-semibold mb-4") Быстрые ссылки
+                    ul(class="space-y-2")
+                        li: app-link(to="/events" class="text-gray-300 hover:text-white transition-colors") Мероприятия
+                        li: app-link(to="/blog" class="text-gray-300 hover:text-white transition-colors") Блог
+                        li: app-link(to="/products" class="text-gray-300 hover:text-white transition-colors") Магазин
+                        li: app-link(to="/about" class="text-gray-300 hover:text-white transition-colors") О нас
+                
+                //- Contact Info
+                div
+                    h3(class="text-lg font-semibold mb-4") Контакты
+                    div(class="text-gray-300 space-y-2")
+                        p г. Душанбе, пр. Рудаки 22
+                        p +992 37 123-45-67
+                        p info@borbad.s5l.ru
+                
+                //- Social Links
+                div
+                    h3(class="text-lg font-semibold mb-4") Мы в соцсетях
+                    div(class="flex space-x-4")
+                        a(href="#" class="text-gray-300 hover:text-white transition-colors") Facebook
+                        a(href="#" class="text-gray-300 hover:text-white transition-colors") Instagram
+                        a(href="#" class="text-gray-300 hover:text-white transition-colors") Twitter
+            
+            //- Copyright
+            div(class="border-t border-gray-700 mt-8 pt-8 text-center text-gray-300")
+                p &copy; 2024 Кохи Борбад. Все права защищены.
 
-    ModalWindow(
-        v-if="modalState.currentModal == 'SuccessModal'"
-        :isVisible="true"
-        @update:isVisible="closeModal"
-        :title="modalState.modalProps.title || 'Успешно!'"
-        :contentClass="'max-w-md'"
-        :showFooter="false"
-    )
-        template([body])
-            SuccessModal(
-                :title="modalState.modalProps.title"
-                :content="modalState.modalProps.content"
-                :isVisible="true"
-                @update:isVisible="closeModal"
+    //- Modal Window
+    div(v-if="modalState.isVisible" class="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50 p-4")
+        div(class="bg-white dark:bg-gray-800 rounded-lg shadow-xl max-w-2xl w-full max-h-90vh overflow-auto")
+            component(
+                :is="modalState.component"
+                v-bind="modalState.props"
+                @close="closeModal"
             )