Gogs пре 1 месец
родитељ
комит
6ecea988a2

+ 3 - 0
README.md

@@ -518,6 +518,8 @@ https://cdn1.ozone.ru/s3/multimedia-1-p/7663352533.jpg";;;ЭкоКрас;4673764
 - Каждый компонент в отдельной папке с `index.pug`, `index.coffee`, `index.styl`
 - Использование render функций: `(new Function '_ctx', '_cache', renderFns[...])()`
 - Централизованная дизайн-система в `theme.config.coffee`
+- ВАЖНО всегда приводи полные листинги файлов
+- ВАЖНО делай верстку всех страниц адаптивной, корректно отображающейся на основных видах устройств
 
 ### 2. **Работа с данными**
 - Селективная синхронизация: общие данные → локальная БД, пользовательские данные → удаленная БД
@@ -527,6 +529,7 @@ https://cdn1.ozone.ru/s3/multimedia-1-p/7663352533.jpg";;;ЭкоКрас;4673764
 ### 3. **Обработка ошибок**
 - Глобальная обработка ошибок, с максимальной детализацией, и возможностью отключения их вывода, в продакшн релизе.
 - Пользовательские уведомления об ошибках
+- ВАЖНО в место console.log используй debug.log
 
 ### 4. **Производительность**
 - Ленивая загрузка компонентов через Vue Router

+ 3 - 5
app/index.coffee

@@ -17,8 +17,6 @@ tailwind.config = {
 
 
 
-
-
 PouchDBService = require 'app/utils/pouch.coffee'
 
 # Инициализация сервиса данных
@@ -104,14 +102,14 @@ router = VueRouter.createRouter({
       path: '/admin',
       component: require 'app/pages/Admin'
       meta: { requiresAdmin: true }
-      #redirect: '/admin/settings'
+      redirect: '/admin/settings'
       children: [
         #{ path: 'slider', component: require 'app/pages/Admin/Slider' }
         #{ path: 'products', component: require 'app/pages/Admin/Products' }
         #{ path: 'clients', component: require 'app/pages/Admin/Clients' }
         #{ path: 'blog', component: require 'app/pages/Admin/Blog' }
         #{ path: 'routes', component: require 'app/pages/Admin/Routes' }
-        #{ path: 'settings', component: require 'app/pages/Admin/Settings' }
+        { path: 'settings', component: require 'app/pages/Admin/Settings' }
       ]
     },
     #{ path: '/catalog', component: require 'app/pages/Catalog' },
@@ -154,4 +152,4 @@ app.config.errorHandler = (err, vm, info) ->
 # Монтирование приложения
 app.mount('body')
 
-console.log 'Приложение Браер-Колор инициализировано'
+debug.log 'Приложение Браер-Колор инициализировано'

+ 36 - 43
app/index.styl

@@ -1,31 +1,4 @@
 @css {
-  /* Определение кастомных цветов для Tailwind */
-  @layer base {
-    :root {
-      --color-primary-50: #fef2f2;
-      --color-primary-100: #fee2e2;
-      --color-primary-200: #fecaca;
-      --color-primary-300: #fca5a5;
-      --color-primary-400: #f87171;
-      --color-primary-500: #ef4444;
-      --color-primary-600: #dc2626;
-      --color-primary-700: #b91c1c;
-      --color-primary-800: #991b1b;
-      --color-primary-900: #7f1d1d;
-      
-      --color-accent-50: #f0f9ff;
-      --color-accent-100: #e0f2fe;
-      --color-accent-200: #bae6fd;
-      --color-accent-300: #7dd3fc;
-      --color-accent-400: #38bdf8;
-      --color-accent-500: #0ea5e9;
-      --color-accent-600: #0284c7;
-      --color-accent-700: #0369a1;
-      --color-accent-800: #075985;
-      --color-accent-900: #0c4a6e;
-    }
-  }
-
   /* Основные стили приложения */
   #app {
     @apply min-h-screen bg-white dark:bg-gray-900 transition-colors duration-300;
@@ -33,7 +6,7 @@
   
   /* Header */
   .header {
-    @apply bg-white dark:bg-gray-800 shadow-sm sticky top-0 z-50;
+    @apply bg-white dark:bg-gray-800 shadow-sm sticky top-0 z-40;
   }
   
   .header__nav {
@@ -41,15 +14,15 @@
   }
   
   .header__nav-block {
-    @apply flex items-center justify-between;
+    @apply flex flex-col sm:flex-row sm:items-center sm:justify-between gap-3;
   }
   
   .header__nav-name {
-    @apply text-2xl font-bold bg-gradient-to-r from-primary-500 to-accent-500 bg-clip-text text-transparent;
+    @apply text-xl sm:text-2xl font-bold bg-gradient-to-r from-primary-500 to-accent-500 bg-clip-text text-transparent text-center sm:text-left;
   }
   
   .header__nav-menu {
-    @apply flex items-center space-x-4;
+    @apply flex flex-wrap justify-center sm:justify-end items-center gap-2 sm:gap-4;
   }
   
   .header__menu-item {
@@ -57,16 +30,16 @@
   }
   
   .header__menu-link {
-    @apply text-gray-600 dark:text-gray-300 hover:text-primary-500 transition-colors duration-200;
+    @apply text-gray-600 dark:text-gray-300 hover:text-primary-500 transition-colors duration-200 text-sm sm:text-base px-2 py-1 rounded;
   }
   
   .header__theme-toggle {
-    @apply p-2 rounded-lg bg-gray-100 dark:bg-gray-700 text-gray-600 dark:text-gray-300 hover:bg-gray-200 dark:hover:bg-gray-600 transition-colors duration-200;
+    @apply p-2 rounded-lg bg-gray-100 dark:bg-gray-700 text-gray-600 dark:text-gray-300 hover:bg-gray-200 dark:hover:bg-gray-600 transition-colors duration-200 text-sm;
   }
   
   /* Main content */
   .main {
-    @apply flex-1;
+    @apply flex-1 container mx-auto px-4 py-6;
   }
   
   /* Page transitions */
@@ -93,19 +66,19 @@
   }
   
   .footer__sections {
-    @apply grid grid-cols-1 md:grid-cols-3 gap-8;
+    @apply grid grid-cols-1 md:grid-cols-3 gap-6 md:gap-8;
   }
   
   .footer__section {
-    @apply flex flex-col;
+    @apply flex flex-col text-center md:text-left;
   }
   
   .footer__section-title {
-    @apply text-lg font-bold mb-4;
+    @apply text-lg font-bold mb-3 md:mb-4;
   }
   
   .footer__section-text {
-    @apply text-gray-400 mb-2;
+    @apply text-gray-400 mb-2 text-sm md:text-base;
   }
   
   .footer__links {
@@ -113,29 +86,38 @@
   }
   
   .footer__link {
-    @apply text-gray-400 hover:text-white transition-colors duration-200;
+    @apply text-gray-400 hover:text-white transition-colors duration-200 text-sm md:text-base;
   }
   
   /* Утилитарные классы */
   .btn-primary {
-    @apply bg-primary-500 hover:bg-primary-600 text-white px-4 py-2 rounded-lg transition-colors duration-200 font-medium;
+    @apply bg-primary-500 hover:bg-primary-600 text-white px-4 py-2 rounded-lg transition-colors duration-200 font-medium text-sm sm:text-base;
   }
   
   .btn-secondary {
-    @apply bg-gray-200 hover:bg-gray-300 dark:bg-gray-700 dark:hover:bg-gray-600 text-gray-800 dark:text-white px-4 py-2 rounded-lg transition-colors duration-200 font-medium;
+    @apply bg-gray-200 hover:bg-gray-300 dark:bg-gray-700 dark:hover:bg-gray-600 text-gray-800 dark:text-white px-4 py-2 rounded-lg transition-colors duration-200 font-medium text-sm sm:text-base;
   }
   
   .card {
-    @apply bg-white dark:bg-gray-800 rounded-lg shadow-md p-6;
+    @apply bg-white dark:bg-gray-800 rounded-lg shadow-md p-4 sm:p-6;
   }
   
   .form-input {
-    @apply w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-lg bg-white dark:bg-gray-700 text-gray-900 dark:text-white focus:outline-none focus:ring-2 focus:ring-primary-500 focus:border-transparent;
+    @apply w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-lg bg-white dark:bg-gray-700 text-gray-900 dark:text-white focus:outline-none focus:ring-2 focus:ring-primary-500 focus:border-transparent text-sm sm:text-base;
   }
   
   .form-label {
     @apply block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2;
   }
+
+  /* Mobile menu */
+  .mobile-menu-btn {
+    @apply sm:hidden p-2 rounded-lg bg-gray-100 dark:bg-gray-700 text-gray-600 dark:text-gray-300;
+  }
+
+  .mobile-menu {
+    @apply sm:hidden absolute top-full left-0 right-0 bg-white dark:bg-gray-800 shadow-lg border-t border-gray-200 dark:border-gray-700;
+  }
 }
 
 /* Темная тема */
@@ -144,3 +126,14 @@
     color-scheme: dark;
   }
 }
+
+/* Медиа-запросы для очень маленьких экранов */
+@media (max-width: 360px) {
+  .header__nav-menu {
+    @apply gap-1;
+  }
+  
+  .header__menu-link {
+    @apply text-xs px-1;
+  }
+}

+ 178 - 0
app/pages/Admin/Settings/index.coffee

@@ -0,0 +1,178 @@
+document.head.insertAdjacentHTML('beforeend','<style type="text/tailwindcss">'+stylFns['app/pages/Admin/Settings/index.styl']+'</style>')
+
+PouchDB = require 'app/utils/pouch'
+
+module.exports =
+  name: 'AdminSettings'
+  
+  render: (new Function '_ctx', '_cache', renderFns['app/pages/Admin/Settings/index.pug'])()
+  
+  data: ->
+    return {
+      activeTab: 'domains'
+      tabs: [
+        { id: 'domains', name: 'Домены' }
+        { id: 'languages', name: 'Языки' }
+        { id: 'general', name: 'Общие настройки' }
+      ]
+      domains: []
+      languages: [
+        { code: 'ru', name: 'Русский', enabled: true }
+        { code: 'en', name: 'English', enabled: false }
+        { code: 'de', name: 'Deutsch', enabled: false }
+      ]
+      availableLanguages: [
+        { code: 'ru', name: 'Русский' }
+        { code: 'en', name: 'English' }
+        { code: 'de', name: 'Deutsch' }
+      ]
+      generalSettings: {
+        companyName: 'Браер-Колор'
+        notificationEmail: 'admin@braer-color.ru'
+        currency: 'RUB'
+      }
+      showDomainModal: false
+      editingDomain: null
+      domainForm: {
+        domain: ''
+        companyName: ''
+        languages: ['ru']
+      }
+    }
+  
+  computed:
+    currentDomain: ->
+      window.location.hostname
+  
+  methods:
+    getTabClass: (tab) ->
+      baseClass = 'admin-settings__tab'
+      isActive = @activeTab == tab.id
+      
+      if isActive
+        return "#{baseClass} admin-settings__tab--active"
+      else
+        return "#{baseClass} admin-settings__tab--inactive"
+    
+    getLanguageBtnClass: (lang) ->
+      baseClass = 'admin-settings__language-btn'
+      if lang.enabled
+        return "#{baseClass} admin-settings__language-btn--enabled"
+      else
+        return "#{baseClass} admin-settings__language-btn--disabled"
+    
+    loadDomains: ->
+      PouchDB.queryView('admin', 'domain_settings', { include_docs: true })
+        .then (result) =>
+          @domains = result.rows.map (row) -> row.doc
+        .catch (error) =>
+          console.error 'Ошибка загрузки доменов:', error
+          @showNotification 'Ошибка загрузки доменов', 'error'
+    
+    loadGeneralSettings: ->
+      PouchDB.getDocument('settings:general')
+        .then (settings) =>
+          @generalSettings = { ...@generalSettings, ...settings }
+        .catch (error) =>
+          debug.log 'Общие настройки не найдены, используются значения по умолчанию'
+    
+    saveGeneralSettings: ->
+      settingsDoc = {
+        _id: 'settings:general'
+        type: 'settings'
+        ...@generalSettings
+        updatedAt: new Date().toISOString()
+      }
+      
+      PouchDB.saveToRemote(settingsDoc)
+        .then (result) =>
+          @showNotification 'Общие настройки сохранены'
+        .catch (error) =>
+          console.error 'Ошибка сохранения настроек:', error
+          @showNotification 'Ошибка сохранения настроек', 'error'
+    
+    addDomain: ->
+      @editingDomain = null
+      @domainForm = {
+        domain: ''
+        companyName: ''
+        languages: ['ru']
+      }
+      @showDomainModal = true
+    
+    editDomain: (domain) ->
+      @editingDomain = domain
+      @domainForm = {
+        domain: domain.domain
+        companyName: domain.companyName
+        languages: domain.languages || ['ru']
+      }
+      @showDomainModal = true
+    
+    saveDomain: ->
+      if !@domainForm.domain
+        @showNotification 'Введите домен', 'error'
+        return
+      
+      domainDoc = {
+        _id: "domain_settings:#{@domainForm.domain}"
+        type: 'domain_settings'
+        domain: @domainForm.domain
+        companyName: @domainForm.companyName
+        languages: @domainForm.languages
+        createdAt: if @editingDomain then @editingDomain.createdAt else new Date().toISOString()
+        updatedAt: new Date().toISOString()
+      }
+      
+      if @editingDomain
+        domainDoc._rev = @editingDomain._rev
+      
+      PouchDB.saveToRemote(domainDoc)
+        .then (result) =>
+          @showDomainModal = false
+          @loadDomains()
+          @showNotification 'Домен сохранен'
+        .catch (error) =>
+          console.error 'Ошибка сохранения домена:', error
+          @showNotification 'Ошибка сохранения домена', 'error'
+    
+    deleteDomain: (domainId) ->
+      if confirm('Вы уверены, что хотите удалить этот домен?')
+        PouchDB.getDocument(domainId)
+          .then (doc) ->
+            PouchDB.saveToRemote({ ...doc, _deleted: true })
+          .then (result) =>
+            @loadDomains()
+            @showNotification 'Домен удален'
+          .catch (error) =>
+            console.error 'Ошибка удаления домена:', error
+            @showNotification 'Ошибка удаления домена', 'error'
+    
+    toggleLanguage: (langCode) ->
+      language = @languages.find (lang) -> lang.code == langCode
+      if language
+        language.enabled = !language.enabled
+        # Сохраняем настройки языков
+        @saveLanguageSettings()
+    
+    saveLanguageSettings: ->
+      languagesDoc = {
+        _id: 'settings:languages'
+        type: 'settings'
+        languages: @languages.filter((lang) -> lang.enabled).map((lang) -> lang.code)
+        updatedAt: new Date().toISOString()
+      }
+      
+      PouchDB.saveToRemote(languagesDoc)
+        .then (result) =>
+          @showNotification 'Настройки языков обновлены'
+        .catch (error) =>
+          console.error 'Ошибка сохранения языков:', error
+          @showNotification 'Ошибка сохранения языков', 'error'
+    
+    showNotification: (message, type = 'success') ->
+      @$root.showNotification?(message, type) || debug.log("#{type}: #{message}")
+  
+  mounted: ->
+    @loadDomains()
+    @loadGeneralSettings()

+ 152 - 0
app/pages/Admin/Settings/index.pug

@@ -0,0 +1,152 @@
+div(class="admin-settings")
+  div(class="admin-settings__header")
+    h1(class="admin-settings__title") Настройки системы
+    p(class="admin-settings__subtitle") Управление доменами, языками и основными параметрами
+  
+  div(class="admin-settings__content")
+    div(class="admin-settings__tabs")
+      button(
+        v-for="tab in tabs"
+        :key="tab.id"
+        @click="activeTab = tab.id"
+        :class="getTabClass(tab)"
+      ) {{ tab.name }}
+    
+    div(class="admin-settings__tab-content")
+      // Вкладка доменов
+      div(v-if="activeTab === 'domains'" class="admin-settings__domains")
+        div(class="admin-settings__section")
+          h2(class="admin-settings__section-title") Управление доменами
+          p(class="admin-settings__section-description") Настройка мультидоменной структуры магазина
+        
+        div(class="admin-settings__domains-list")
+          div(
+            v-for="domain in domains"
+            :key="domain._id"
+            class="admin-settings__domain-item"
+          )
+            div(class="admin-settings__domain-info")
+              h3(class="admin-settings__domain-name") {{ domain.domain }}
+              p(class="admin-settings__domain-desc") {{ domain.companyName }}
+            div(class="admin-settings__domain-actions")
+              button(
+                @click="editDomain(domain)"
+                class="admin-settings__btn admin-settings__btn--secondary"
+              ) Редактировать
+              button(
+                @click="deleteDomain(domain._id)"
+                class="admin-settings__btn admin-settings__btn--danger"
+              ) Удалить
+        
+        button(
+          @click="showDomainModal = true"
+          class="admin-settings__btn admin-settings__btn--primary"
+        ) Добавить домен
+      
+      // Вкладка языков
+      div(v-if="activeTab === 'languages'" class="admin-settings__languages")
+        div(class="admin-settings__section")
+          h2(class="admin-settings__section-title") Настройка языков
+          p(class="admin-settings__section-description") Управление мультиязычностью сайта
+        
+        div(class="admin-settings__languages-list")
+          div(
+            v-for="lang in languages"
+            :key="lang.code"
+            class="admin-settings__language-item"
+          )
+            div(class="admin-settings__language-info")
+              span(class="admin-settings__language-code") {{ lang.code.toUpperCase() }}
+              span(class="admin-settings__language-name") {{ lang.name }}
+            div(class="admin-settings__language-actions")
+              button(
+                @click="toggleLanguage(lang.code)"
+                :class="getLanguageBtnClass(lang)"
+              ) {{ lang.enabled ? 'Отключить' : 'Включить' }}
+      
+      // Вкладка общих настроек
+      div(v-if="activeTab === 'general'" class="admin-settings__general")
+        div(class="admin-settings__section")
+          h2(class="admin-settings__section-title") Общие настройки
+          p(class="admin-settings__section-description") Основные параметры системы
+        
+        div(class="admin-settings__form")
+          div(class="admin-settings__form-group")
+            label(class="admin-settings__label") Название компании
+            input(
+              v-model="generalSettings.companyName"
+              type="text"
+              class="admin-settings__input"
+              placeholder="Введите название компании"
+            )
+          
+          div(class="admin-settings__form-group")
+            label(class="admin-settings__label") Email для уведомлений
+            input(
+              v-model="generalSettings.notificationEmail"
+              type="email"
+              class="admin-settings__input"
+              placeholder="admin@example.com"
+            )
+          
+          div(class="admin-settings__form-group")
+            label(class="admin-settings__label") Валюта
+            select(v-model="generalSettings.currency" class="admin-settings__select")
+              option(value="RUB") RUB - Российский рубль
+              option(value="USD") USD - Доллар США
+              option(value="EUR") EUR - Евро
+          
+          button(
+            @click="saveGeneralSettings"
+            class="admin-settings__btn admin-settings__btn--primary"
+          ) Сохранить настройки
+  
+  // Модальное окно добавления домена
+  div(v-if="showDomainModal" class="admin-settings__modal")
+    div(class="admin-settings__modal-content")
+      h3(class="admin-settings__modal-title") {{ editingDomain ? 'Редактирование' : 'Добавление' }} домена
+      
+      div(class="admin-settings__modal-form")
+        div(class="admin-settings__form-group")
+          label(class="admin-settings__label") Домен
+          input(
+            v-model="domainForm.domain"
+            type="text"
+            class="admin-settings__input"
+            placeholder="example.com"
+          )
+        
+        div(class="admin-settings__form-group")
+          label(class="admin-settings__label") Название компании
+          input(
+            v-model="domainForm.companyName"
+            type="text"
+            class="admin-settings__input"
+            placeholder="Название компании"
+          )
+        
+        div(class="admin-settings__form-group")
+          label(class="admin-settings__label") Языки домена
+          div(class="admin-settings__checkbox-group")
+            label(
+              v-for="lang in availableLanguages"
+              :key="lang.code"
+              class="admin-settings__checkbox-label"
+            )
+              input(
+                type="checkbox"
+                :value="lang.code"
+                v-model="domainForm.languages"
+                class="admin-settings__checkbox"
+              )
+              span {{ lang.name }}
+      
+      div(class="admin-settings__modal-actions")
+        button(
+          @click="saveDomain"
+          class="admin-settings__btn admin-settings__btn--primary"
+        ) {{ editingDomain ? 'Обновить' : 'Добавить' }}
+        button(
+          @click="showDomainModal = false"
+          class="admin-settings__btn admin-settings__btn--secondary"
+        ) Отмена

+ 188 - 0
app/pages/Admin/Settings/index.styl

@@ -0,0 +1,188 @@
+@css {
+  .admin-settings {
+    @apply space-y-4 sm:space-y-6;
+  }
+
+  .admin-settings__header {
+    @apply border-b border-gray-200 dark:border-gray-700 pb-4 sm:pb-6;
+  }
+
+  .admin-settings__title {
+    @apply text-xl sm:text-2xl font-bold text-gray-900 dark:text-white;
+  }
+
+  .admin-settings__subtitle {
+    @apply text-gray-600 dark:text-gray-400 mt-1 sm:mt-2 text-sm sm:text-base;
+  }
+
+  .admin-settings__content {
+    @apply bg-white dark:bg-gray-800 rounded-lg shadow-sm;
+  }
+
+  .admin-settings__tabs {
+    @apply border-b border-gray-200 dark:border-gray-700 flex space-x-2 sm:space-x-8 px-4 sm:px-6 overflow-x-auto;
+  }
+
+  .admin-settings__tab {
+    @apply py-3 sm:py-4 px-2 sm:px-1 border-b-2 font-medium text-sm transition-colors duration-200 whitespace-nowrap flex-shrink-0;
+  }
+
+  .admin-settings__tab--active {
+    @apply border-primary-500 text-primary-600 dark:text-primary-400;
+  }
+
+  .admin-settings__tab--inactive {
+    @apply border-transparent text-gray-500 hover:text-gray-700 dark:hover:text-gray-300 hover:border-gray-300 dark:hover:border-gray-600;
+  }
+
+  .admin-settings__tab-content {
+    @apply p-4 sm:p-6;
+  }
+
+  .admin-settings__section {
+    @apply mb-6 sm:mb-8;
+  }
+
+  .admin-settings__section-title {
+    @apply text-base sm:text-lg font-medium text-gray-900 dark:text-white mb-2;
+  }
+
+  .admin-settings__section-description {
+    @apply text-gray-600 dark:text-gray-400 mb-3 sm:mb-4 text-sm sm:text-base;
+  }
+
+  .admin-settings__domains-list {
+    @apply space-y-3 sm:space-y-4 mb-4 sm:mb-6;
+  }
+
+  .admin-settings__domain-item {
+    @apply flex flex-col sm:flex-row sm:items-center sm:justify-between p-3 sm:p-4 border border-gray-200 dark:border-gray-700 rounded-lg gap-3;
+  }
+
+  .admin-settings__domain-info {
+    @apply flex-1;
+  }
+
+  .admin-settings__domain-name {
+    @apply font-medium text-gray-900 dark:text-white text-sm sm:text-base;
+  }
+
+  .admin-settings__domain-desc {
+    @apply text-gray-600 dark:text-gray-400 text-xs sm:text-sm;
+  }
+
+  .admin-settings__domain-actions {
+    @apply flex space-x-2 self-end sm:self-auto;
+  }
+
+  .admin-settings__languages-list {
+    @apply grid grid-cols-1 sm:grid-cols-2 gap-3 sm:gap-4;
+  }
+
+  .admin-settings__language-item {
+    @apply flex items-center justify-between p-3 sm:p-4 border border-gray-200 dark:border-gray-700 rounded-lg;
+  }
+
+  .admin-settings__language-info {
+    @apply flex items-center space-x-2 sm:space-x-3;
+  }
+
+  .admin-settings__language-code {
+    @apply bg-gray-100 dark:bg-gray-700 px-2 py-1 rounded text-xs sm:text-sm font-medium;
+  }
+
+  .admin-settings__language-name {
+    @apply text-gray-700 dark:text-gray-300 text-sm sm:text-base;
+  }
+
+  .admin-settings__language-btn {
+    @apply px-2 sm:px-3 py-1 rounded text-xs sm:text-sm font-medium transition-colors duration-200 whitespace-nowrap;
+  }
+
+  .admin-settings__language-btn--enabled {
+    @apply bg-red-100 text-red-700 hover:bg-red-200 dark:bg-red-900 dark:text-red-300 dark:hover:bg-red-800;
+  }
+
+  .admin-settings__language-btn--disabled {
+    @apply bg-green-100 text-green-700 hover:bg-green-200 dark:bg-green-900 dark:text-green-300 dark:hover:bg-green-800;
+  }
+
+  .admin-settings__form {
+    @apply max-w-full sm:max-w-md space-y-3 sm:space-y-4;
+  }
+
+  .admin-settings__form-group {
+    @apply space-y-1 sm:space-y-2;
+  }
+
+  .admin-settings__label {
+    @apply block text-sm font-medium text-gray-700 dark:text-gray-300;
+  }
+
+  .admin-settings__input {
+    @apply w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-lg bg-white dark:bg-gray-700 text-gray-900 dark:text-white focus:outline-none focus:ring-2 focus:ring-primary-500 focus:border-transparent text-sm sm:text-base;
+  }
+
+  .admin-settings__select {
+    @apply w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-lg bg-white dark:bg-gray-700 text-gray-900 dark:text-white focus:outline-none focus:ring-2 focus:ring-primary-500 focus:border-transparent text-sm sm:text-base;
+  }
+
+  .admin-settings__checkbox-group {
+    @apply space-y-2;
+  }
+
+  .admin-settings__checkbox-label {
+    @apply flex items-center space-x-2 text-gray-700 dark:text-gray-300 text-sm sm:text-base;
+  }
+
+  .admin-settings__checkbox {
+    @apply rounded border-gray-300 text-primary-600 focus:ring-primary-500 dark:border-gray-600 dark:bg-gray-700 w-4 h-4;
+  }
+
+  .admin-settings__btn {
+    @apply px-3 sm:px-4 py-2 rounded-lg font-medium transition-colors duration-200 focus:outline-none focus:ring-2 focus:ring-offset-2 text-sm sm:text-base;
+  }
+
+  .admin-settings__btn--primary {
+    @apply bg-primary-600 text-white hover:bg-primary-700 focus:ring-primary-500;
+  }
+
+  .admin-settings__btn--secondary {
+    @apply bg-gray-200 text-gray-700 hover:bg-gray-300 focus:ring-gray-500 dark:bg-gray-700 dark:text-gray-300 dark:hover:bg-gray-600;
+  }
+
+  .admin-settings__btn--danger {
+    @apply bg-red-600 text-white hover:bg-red-700 focus:ring-red-500;
+  }
+
+  .admin-settings__modal {
+    @apply fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50 p-4;
+  }
+
+  .admin-settings__modal-content {
+    @apply bg-white dark:bg-gray-800 rounded-lg p-4 sm:p-6 max-w-md w-full mx-auto max-h-[90vh] overflow-y-auto;
+  }
+
+  .admin-settings__modal-title {
+    @apply text-lg font-medium text-gray-900 dark:text-white mb-3 sm:mb-4;
+  }
+
+  .admin-settings__modal-form {
+    @apply space-y-3 sm:space-y-4 mb-4 sm:mb-6;
+  }
+
+  .admin-settings__modal-actions {
+    @apply flex flex-col sm:flex-row space-y-2 sm:space-y-0 sm:space-x-3 justify-end;
+  }
+}
+
+/* Медиа-запросы для очень маленьких экранов */
+@media (max-width: 360px) {
+  .admin-settings__domain-actions {
+    @apply flex-col space-y-2 space-x-0;
+  }
+  
+  .admin-settings__btn {
+    @apply px-2 py-1 text-xs;
+  }
+}

+ 122 - 0
app/pages/Admin/index.coffee

@@ -0,0 +1,122 @@
+document.head.insertAdjacentHTML('beforeend','<style type="text/tailwindcss">'+stylFns['app/pages/Admin/index.styl']+'</style>')
+
+PouchDB = require 'app/utils/pouch'
+
+# Иконки для меню (упрощенные компоненты)
+MenuIcons =
+  SliderIcon:
+    template: '<svg fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 8h16M4 16h16"></path></svg>'
+  
+  ProductsIcon:
+    template: '<svg fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M20 7l-8-4-8 4m16 0l-8 4m8-4v10l-8 4m0-10L4 7m8 4v10M4 7v10l8 4"></path></svg>'
+  
+  ClientsIcon:
+    template: '<svg fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M17 20h5v-2a3 3 0 00-5.356-1.857M17 20H7m10 0v-2c0-.656-.126-1.283-.356-1.857M7 20H2v-2a3 3 0 015.356-1.857M7 20v-2c0-.656.126-1.283.356-1.857m0 0a5.002 5.002 0 019.288 0M15 7a3 3 0 11-6 0 3 3 0 016 0zm6 3a2 2 0 11-4 0 2 2 0 014 0zM7 10a2 2 0 11-4 0 2 2 0 014 0z"></path></svg>'
+  
+  BlogIcon:
+    template: '<svg fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M11 5H6a2 2 0 00-2 2v11a2 2 0 002 2h11a2 2 0 002-2v-5m-1.414-9.414a2 2 0 112.828 2.828L11.828 15H9v-2.828l8.586-8.586z"></path></svg>'
+  
+  RoutesIcon:
+    template: '<svg fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13.828 10.172a4 4 0 00-5.656 0l-4 4a4 4 0 105.656 5.656l1.102-1.101m-.758-4.899a4 4 0 005.656 0l4-4a4 4 0 00-5.656-5.656l-1.1 1.1"></path></svg>'
+  
+  SettingsIcon:
+    template: '<svg fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10.325 4.317c.426-1.756 2.924-1.756 3.35 0a1.724 1.724 0 002.573 1.066c1.543-.94 3.31.826 2.37 2.37a1.724 1.724 0 001.065 2.572c1.756.426 1.756 2.924 0 3.35a1.724 1.724 0 00-1.066 2.573c.94 1.543-.826 3.31-2.37 2.37a1.724 1.724 0 00-2.572 1.065c-.426 1.756-2.924 1.756-3.35 0a1.724 1.724 0 00-2.573-1.066c-1.543.94-3.31-.826-2.37-2.37a1.724 1.724 0 00-1.065-2.572c-1.756-.426-1.756-2.924 0-3.35a1.724 1.724 0 001.066-2.573c-.94-1.543.826-3.31 2.37-2.37.996.608 2.296.07 2.572-1.065z"></path><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 12a3 3 0 11-6 0 3 3 0 016 0z"></path></svg>'
+
+module.exports =
+  name: 'AdminPanel'
+  components: MenuIcons
+  
+  render: (new Function '_ctx', '_cache', renderFns['app/pages/Admin/index.pug'])()
+  
+  data: ->
+    return {
+      currentDomain: window.location.hostname
+      mobileMenuOpen: false
+      menuItems: [
+        {
+          id: 'slider'
+          name: 'Слайдер'
+          path: '/admin/slider'
+          icon: 'SliderIcon'
+        }
+        {
+          id: 'products'
+          name: 'Товары'
+          path: '/admin/products'
+          icon: 'ProductsIcon'
+        }
+        {
+          id: 'clients'
+          name: 'Клиенты'
+          path: '/admin/clients'
+          icon: 'ClientsIcon'
+        }
+        {
+          id: 'blog'
+          name: 'Блог'
+          path: '/admin/blog'
+          icon: 'BlogIcon'
+        }
+        {
+          id: 'routes'
+          name: 'Маршруты'
+          path: '/admin/routes'
+          icon: 'RoutesIcon'
+        }
+        {
+          id: 'settings'
+          name: 'Настройки'
+          path: '/admin/settings'
+          icon: 'SettingsIcon'
+        }
+      ]
+    }
+  
+  computed:
+    currentRoute: ->
+      @$route.path.split('/').pop() || 'settings'
+    
+    showMobileMenuButton: ->
+      window.innerWidth < 1024
+  
+  methods:
+    navigateTo: (path) ->
+      @mobileMenuOpen = false
+      @$router.push(path)
+    
+    toggleMobileMenu: ->
+      @mobileMenuOpen = !@mobileMenuOpen
+    
+    getMenuItemClass: (item) ->
+      baseClass = 'admin__nav-item'
+      isActive = @currentRoute == item.id
+      
+      if isActive
+        return "#{baseClass} admin__nav-item--active"
+      else
+        return "#{baseClass} admin__nav-item--inactive"
+    
+    getSidebarClass: ->
+      if @mobileMenuOpen
+        return 'admin__sidebar--visible'
+      else
+        return 'admin__sidebar--hidden'
+    
+    loadDomainSettings: ->
+      PouchDB.getDocument("domain_settings:#{@currentDomain}")
+        .then (settings) =>
+          @domainSettings = settings
+        .catch (error) =>
+          debug.log 'Настройки домена не найдены, используются значения по умолчанию'
+          @domainSettings = null
+    
+    handleResize: ->
+      if window.innerWidth >= 1024
+        @mobileMenuOpen = false
+  
+  mounted: ->
+    @loadDomainSettings()
+    window.addEventListener 'resize', @handleResize
+  
+  beforeUnmount: ->
+    window.removeEventListener 'resize', @handleResize

+ 52 - 0
app/pages/Admin/index.pug

@@ -0,0 +1,52 @@
+div(class="admin")
+  header(class="admin__header")
+    div(class="admin__header-content")
+      div(class="admin__header-top")
+        h1(class="admin__header-title") Панель администратора
+        button(
+          @click="toggleMobileMenu"
+          class="admin__mobile-menu-btn"
+          v-if="showMobileMenuButton"
+        )
+          svg(v-if="!mobileMenuOpen" fill="none" stroke="currentColor" viewBox="0 0 24 24" class="w-6 h-6")
+            path(stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 6h16M4 12h16M4 18h16")
+          svg(v-else fill="none" stroke="currentColor" viewBox="0 0 24 24" class="w-6 h-6")
+            path(stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12")
+      div(class="admin__domain-info") Текущий домен: {{ currentDomain }}
+  
+  //- Mobile menu (positioned above header)
+  div(
+    v-if="mobileMenuOpen"
+    @click="toggleMobileMenu"
+    class="admin__sidebar-overlay"
+  )
+  
+  div(
+    :class="getSidebarClass()"
+    class="admin__sidebar"
+  )
+    div(class="admin__sidebar-header")
+      h3(class="admin__sidebar-title") Меню
+      button(
+        @click="toggleMobileMenu"
+        class="admin__sidebar-close"
+      )
+        svg(fill="none" stroke="currentColor" viewBox="0 0 24 24" class="w-6 h-6")
+          path(stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12")
+    
+    nav(class="admin__nav")
+      a(
+        v-for="item in menuItems"
+        :key="item.id"
+        :href="item.path"
+        @click.prevent="navigateTo(item.path)"
+        :class="getMenuItemClass(item)"
+      )
+        div(class="admin__nav-item-content")
+          component(:is="item.icon" class="admin__nav-icon")
+          span(class="admin__nav-text") {{ item.name }}
+  
+  div(class="admin__body")
+    //- Main content
+    main(class="admin__main")
+      router-view

+ 127 - 0
app/pages/Admin/index.styl

@@ -0,0 +1,127 @@
+@css {
+  .admin {
+    @apply min-h-screen flex flex-col bg-gray-50 dark:bg-gray-900;
+  }
+
+  /* Header */
+  .admin__header {
+    @apply bg-white dark:bg-gray-800 shadow-sm z-40 sticky top-0;
+  }
+
+  .admin__header-content {
+    @apply px-4 sm:px-6 py-3 sm:py-4;
+  }
+
+  .admin__header-top {
+    @apply flex items-center justify-between;
+  }
+
+  .admin__header-title {
+    @apply text-lg sm:text-xl font-bold text-gray-800 dark:text-white;
+  }
+
+  .admin__domain-info {
+    @apply text-xs sm:text-sm text-gray-600 dark:text-gray-400 mt-2;
+  }
+
+  .admin__mobile-menu-btn {
+    @apply lg:hidden p-2 rounded-lg bg-gray-100 dark:bg-gray-700 text-gray-600 dark:text-gray-300 hover:bg-gray-200 dark:hover:bg-gray-600 transition-colors duration-200;
+  }
+
+  /* Body layout */
+  .admin__body {
+    @apply flex flex-1 flex-col lg:flex-row;
+  }
+
+  /* Sidebar - positioned above header in mobile */
+  .admin__sidebar {
+    @apply w-full lg:w-64 bg-white dark:bg-gray-800 shadow-2xl lg:shadow-none z-50 fixed lg:static inset-0 transform transition-transform duration-300 ease-in-out;
+  }
+
+  .admin__sidebar--hidden {
+    @apply -translate-x-full lg:translate-x-0;
+  }
+
+  .admin__sidebar--visible {
+    @apply translate-x-0;
+  }
+
+  .admin__sidebar-overlay {
+    @apply lg:hidden fixed inset-0 bg-black bg-opacity-50 z-40;
+  }
+
+  .admin__sidebar-header {
+    @apply lg:hidden flex items-center justify-between p-4 border-b border-gray-200 dark:border-gray-700 bg-white dark:bg-gray-800;
+  }
+
+  .admin__sidebar-title {
+    @apply text-lg font-medium text-gray-800 dark:text-white;
+  }
+
+  .admin__sidebar-close {
+    @apply lg:hidden p-2 rounded-lg bg-gray-100 dark:bg-gray-700 text-gray-600 dark:text-gray-300 hover:bg-gray-200 dark:hover:bg-gray-600 transition-colors duration-200;
+  }
+
+  .admin__nav {
+    @apply p-4 space-y-2 h-full overflow-y-auto;
+  }
+
+  .admin__nav-item {
+    @apply flex items-center space-x-3 px-3 sm:px-4 py-2 sm:py-3 rounded-lg transition-colors duration-200 text-sm sm:text-base;
+  }
+
+  .admin__nav-item--active {
+    @apply bg-primary-100 dark:bg-primary-900 text-primary-700 dark:text-primary-300;
+  }
+
+  .admin__nav-item--inactive {
+    @apply text-gray-600 dark:text-gray-400 hover:bg-gray-100 dark:hover:bg-gray-700 hover:text-gray-900 dark:hover:text-white;
+  }
+
+  .admin__nav-item-content {
+    @apply flex items-center space-x-3;
+  }
+
+  .admin__nav-icon {
+    @apply w-4 h-4 sm:w-5 sm:h-5 flex-shrink-0;
+  }
+
+  .admin__nav-text {
+    @apply font-medium truncate;
+  }
+
+  /* Main content */
+  .admin__main {
+    @apply flex-1 p-4 sm:p-6 lg:p-8 overflow-auto min-h-0;
+  }
+}
+
+/* Медиа-запросы для планшетов */
+@media (max-width: 1024px) {
+  .admin__sidebar {
+    @apply w-80;
+  }
+}
+
+/* Медиа-запросы для очень маленьких экранов */
+@media (max-width: 360px) {
+  .admin__header-content {
+    @apply px-3 py-2;
+  }
+  
+  .admin__header-title {
+    @apply text-base;
+  }
+  
+  .admin__nav {
+    @apply p-3;
+  }
+  
+  .admin__nav-item {
+    @apply px-2 py-2 text-xs;
+  }
+  
+  .admin__main {
+    @apply p-3;
+  }
+}

+ 1 - 1
app/pages/Home/index.coffee

@@ -10,7 +10,7 @@ module.exports =
     }
   
   mounted: ->
-    console.log 'Главная страница загружена'
+    debug.log 'Главная страница загружена'
   
   methods:
     navigateToCatalog: ->