From abd0df1fe782cb885c9676d75966264d411d1551 Mon Sep 17 00:00:00 2001 From: Bivekich Date: Thu, 17 Jul 2025 12:26:08 +0300 Subject: [PATCH] =?UTF-8?q?=D0=94=D0=BE=D0=B1=D0=B0=D0=B2=D0=BB=D0=B5?= =?UTF-8?q?=D0=BD=D1=8B=20=D0=BD=D0=B0=D1=81=D1=82=D1=80=D0=BE=D0=B9=D0=BA?= =?UTF-8?q?=D0=B8=20=D0=B4=D0=BB=D1=8F=20=D1=83=D0=B4=D0=B0=D0=BB=D0=B5?= =?UTF-8?q?=D0=BD=D0=BD=D1=8B=D1=85=20=D0=B8=D0=B7=D0=BE=D0=B1=D1=80=D0=B0?= =?UTF-8?q?=D0=B6=D0=B5=D0=BD=D0=B8=D0=B9=20=D0=B2=20=D0=BA=D0=BE=D0=BD?= =?UTF-8?q?=D1=84=D0=B8=D0=B3=D1=83=D1=80=D0=B0=D1=86=D0=B8=D1=8E=20Next.j?= =?UTF-8?q?s.=20=D0=A3=D0=B4=D0=B0=D0=BB=D0=B5=D0=BD=D1=8B=20=D0=BD=D0=B5?= =?UTF-8?q?=D0=B8=D1=81=D0=BF=D0=BE=D0=BB=D1=8C=D0=B7=D1=83=D0=B5=D0=BC?= =?UTF-8?q?=D1=8B=D0=B5=20=D0=B7=D0=B0=D0=B3=D0=BE=D0=BB=D0=BE=D0=B2=D0=BA?= =?UTF-8?q?=D0=B8=20=D0=B8=20=D1=83=D0=BB=D1=83=D1=87=D1=88=D0=B5=D0=BD=20?= =?UTF-8?q?=D0=B8=D0=BD=D1=82=D0=B5=D1=80=D1=84=D0=B5=D0=B9=D1=81=20=D0=BA?= =?UTF-8?q?=D0=BE=D0=BC=D0=BF=D0=BE=D0=BD=D0=B5=D0=BD=D1=82=D0=BE=D0=B2=20?= =?UTF-8?q?=D0=BF=D0=B0=D0=BD=D0=B5=D0=BB=D0=B8=20=D1=83=D0=BF=D1=80=D0=B0?= =?UTF-8?q?=D0=B2=D0=BB=D0=B5=D0=BD=D0=B8=D1=8F=20=D0=B8=20=D0=BC=D0=B5?= =?UTF-8?q?=D1=81=D1=81=D0=B5=D0=BD=D0=B4=D0=B6=D0=B5=D1=80=D0=B0,=20?= =?UTF-8?q?=D0=B2=D0=BA=D0=BB=D1=8E=D1=87=D0=B0=D1=8F=20=D0=B4=D0=BE=D0=B1?= =?UTF-8?q?=D0=B0=D0=B2=D0=BB=D0=B5=D0=BD=D0=B8=D0=B5=20=D0=BD=D0=BE=D0=B2?= =?UTF-8?q?=D1=8B=D1=85=20=D0=B7=D0=B0=D0=B3=D0=BE=D0=BB=D0=BE=D0=B2=D0=BA?= =?UTF-8?q?=D0=BE=D0=B2=20=D1=81=20=D0=BA=D0=BD=D0=BE=D0=BF=D0=BA=D0=B0?= =?UTF-8?q?=D0=BC=D0=B8=20=D0=B8=20=D1=81=D0=BE=D0=BA=D1=80=D0=B0=D1=89?= =?UTF-8?q?=D0=B5=D0=BD=D0=BD=D1=8B=D1=85=20=D0=BD=D0=B0=D0=B7=D0=B2=D0=B0?= =?UTF-8?q?=D0=BD=D0=B8=D0=B9=20=D0=BA=D0=BE=D0=BC=D0=BF=D0=B0=D0=BD=D0=B8?= =?UTF-8?q?=D0=B9.=20=D0=9E=D0=BF=D1=82=D0=B8=D0=BC=D0=B8=D0=B7=D0=B8?= =?UTF-8?q?=D1=80=D0=BE=D0=B2=D0=B0=D0=BD=D1=8B=20=D1=81=D1=82=D0=B8=D0=BB?= =?UTF-8?q?=D0=B8=20=D0=B8=20=D1=81=D1=82=D1=80=D1=83=D0=BA=D1=82=D1=83?= =?UTF-8?q?=D1=80=D0=B0=20=D0=BA=D0=BE=D0=B4=D0=B0=20=D0=B2=20=D0=BA=D0=BE?= =?UTF-8?q?=D0=BC=D0=BF=D0=BE=D0=BD=D0=B5=D0=BD=D1=82=D0=B0=D1=85.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- deploy.sh | 37 +++ docker-compose.prod.yml | 36 +++ next.config.ts | 10 + src/components/dashboard/dashboard-home.tsx | 9 - src/components/dashboard/user-settings.tsx | 294 +++++++++++++----- src/components/market/market-dashboard.tsx | 8 - src/components/messenger/messenger-chat.tsx | 45 ++- .../messenger/messenger-conversations.tsx | 44 ++- .../messenger/messenger-dashboard.tsx | 8 - .../services/services-dashboard.tsx | 8 - 10 files changed, 375 insertions(+), 124 deletions(-) create mode 100644 deploy.sh create mode 100644 docker-compose.prod.yml diff --git a/deploy.sh b/deploy.sh new file mode 100644 index 0000000..5f63f1b --- /dev/null +++ b/deploy.sh @@ -0,0 +1,37 @@ +#!/bin/bash + +echo "🚀 Starting deployment to new.sferav.ru..." + +# Остановка предыдущей версии +echo "⏹️ Stopping previous version..." +docker-compose -f docker-compose.prod.yml down + +# Очистка неиспользуемых образов +echo "🧹 Cleaning up unused images..." +docker image prune -f + +# Сборка и запуск новой версии +echo "🔨 Building and starting new version..." +docker-compose -f docker-compose.prod.yml up -d --build + +# Ожидание запуска +echo "⏳ Waiting for application to start..." +sleep 10 + +# Проверка здоровья +echo "🏥 Checking application health..." +for i in {1..30}; do + if curl -f http://127.0.0.1:3017/api/health > /dev/null 2>&1; then + echo "✅ Application is healthy!" + break + fi + echo "⏳ Attempt $i/30 - waiting for health check..." + sleep 2 +done + +# Проверка статуса контейнера +echo "📊 Container status:" +docker-compose -f docker-compose.prod.yml ps + +echo "🎉 Deployment completed!" +echo "🌐 Application is available at: https://new.sferav.ru" \ No newline at end of file diff --git a/docker-compose.prod.yml b/docker-compose.prod.yml new file mode 100644 index 0000000..24560a3 --- /dev/null +++ b/docker-compose.prod.yml @@ -0,0 +1,36 @@ +services: + app: + build: + context: . + args: + - DATABASE_URL=${DATABASE_URL} + - SMS_AERO_EMAIL=${SMS_AERO_EMAIL} + - SMS_AERO_API_KEY=${SMS_AERO_API_KEY} + - SMS_AERO_API_URL=${SMS_AERO_API_URL} + - DADATA_API_KEY=${DADATA_API_KEY} + - DADATA_API_URL=${DADATA_API_URL} + - WILDBERRIES_API_URL=${WILDBERRIES_API_URL} + - OZON_API_URL=${OZON_API_URL} + - JWT_SECRET=${JWT_SECRET} + - SMS_DEV_MODE=${SMS_DEV_MODE} + ports: + - "127.0.0.1:3017:3000" # Привязка только к localhost + env_file: + - .env + environment: + - NODE_ENV=production + - PORT=3000 + - HOSTNAME=0.0.0.0 + restart: unless-stopped + healthcheck: + test: ["CMD", "wget", "--no-verbose", "--tries=1", "--spider", "http://localhost:3000/api/health"] + timeout: 10s + interval: 30s + retries: 3 + start_period: 40s + # Логирование + logging: + driver: "json-file" + options: + max-size: "10m" + max-file: "3" \ No newline at end of file diff --git a/next.config.ts b/next.config.ts index 225e495..dabb079 100644 --- a/next.config.ts +++ b/next.config.ts @@ -2,6 +2,16 @@ import type { NextConfig } from "next"; const nextConfig: NextConfig = { output: 'standalone', + images: { + remotePatterns: [ + { + protocol: 'https', + hostname: 's3.twcstorage.ru', + port: '', + pathname: '/**', + }, + ], + }, }; export default nextConfig; diff --git a/src/components/dashboard/dashboard-home.tsx b/src/components/dashboard/dashboard-home.tsx index e949f9b..b1f02fb 100644 --- a/src/components/dashboard/dashboard-home.tsx +++ b/src/components/dashboard/dashboard-home.tsx @@ -40,15 +40,6 @@ export function DashboardHome() {
-
-

- Добро пожаловать! -

-

- Главная панель управления {getCabinetType()} -

-
-
{/* Информация об организации */} diff --git a/src/components/dashboard/user-settings.tsx b/src/components/dashboard/user-settings.tsx index f5b38e8..05d5f24 100644 --- a/src/components/dashboard/user-settings.tsx +++ b/src/components/dashboard/user-settings.tsx @@ -554,62 +554,6 @@ export function UserSettings() {
- {/* Заголовок - фиксированная высота */} -
-
-

Настройки профиля

-

Управление информацией о профиле и организации

-
-
- {/* Компактный индикатор прогресса */} -
-
- {profileStatus.percentage}% -
-
- {isIncomplete ? ( - <>Заполнено {profileStatus.percentage}% профиля - ) : ( - <>Профиль полностью заполнен - )} -
-
- - {isEditing ? ( - <> - - - - ) : ( - - )} -
-
- {/* Сообщения о сохранении */} {saveMessage && ( @@ -658,6 +602,64 @@ export function UserSettings() { {/* Профиль пользователя */} + {/* Заголовок вкладки с прогрессом и кнопками */} +
+
+ +
+

Профиль пользователя

+

Личная информация и контактные данные

+
+
+
+ {/* Компактный индикатор прогресса */} +
+
+ {profileStatus.percentage}% +
+
+ {isIncomplete ? ( + <>Заполнено {profileStatus.percentage}% профиля + ) : ( + <>Профиль полностью заполнен + )} +
+
+ + {isEditing ? ( + <> + + + + ) : ( + + )} +
+
@@ -831,12 +833,56 @@ export function UserSettings() { {/* Организация и юридические данные */} -
- -

Организация и юридические данные

- {(formData.inn || user?.organization?.inn) && ( - - )} + {/* Заголовок вкладки с кнопками */} +
+
+ +
+

Данные организации

+

Юридическая информация и реквизиты

+
+
+
+ {(formData.inn || user?.organization?.inn) && ( +
+ + Проверено +
+ )} + + {isEditing ? ( + <> + + + + ) : ( + + )} +
{/* Общая подпись про реестр */} @@ -1010,12 +1056,56 @@ export function UserSettings() { {(user?.organization?.type === 'FULFILLMENT' || user?.organization?.type === 'LOGIST' || user?.organization?.type === 'WHOLESALE' || user?.organization?.type === 'SELLER') && ( -
- -

Финансовые данные

- {formData.bankName && formData.bik && formData.accountNumber && formData.corrAccount && ( - - )} + {/* Заголовок вкладки с кнопками */} +
+
+ +
+

Финансовые данные

+

Банковские реквизиты для расчетов

+
+
+
+ {formData.bankName && formData.bik && formData.accountNumber && formData.corrAccount && ( +
+ + Заполнено +
+ )} + + {isEditing ? ( + <> + + + + ) : ( + + )} +
@@ -1073,12 +1163,56 @@ export function UserSettings() { {user?.organization?.type === 'SELLER' && ( -
- -

API ключи маркетплейсов

- {user?.organization?.apiKeys?.length > 0 && ( - - )} + {/* Заголовок вкладки с кнопками */} +
+
+ +
+

API ключи маркетплейсов

+

Интеграция с торговыми площадками

+
+
+
+ {user?.organization?.apiKeys?.length > 0 && ( +
+ + Настроено +
+ )} + + {isEditing ? ( + <> + + + + ) : ( + + )} +
@@ -1123,9 +1257,15 @@ export function UserSettings() { {/* Инструменты */} -
- -

Инструменты

+ {/* Заголовок вкладки */} +
+
+ +
+

Инструменты

+

Дополнительные возможности для бизнеса

+
+
{(user?.organization?.type === 'FULFILLMENT' || user?.organization?.type === 'LOGIST' || user?.organization?.type === 'WHOLESALE') && ( diff --git a/src/components/market/market-dashboard.tsx b/src/components/market/market-dashboard.tsx index 6937aa2..5e07dbd 100644 --- a/src/components/market/market-dashboard.tsx +++ b/src/components/market/market-dashboard.tsx @@ -15,14 +15,6 @@ export function MarketDashboard() {
- {/* Заголовок - фиксированная высота */} -
-
-

Маркет

-

Управление контрагентами и поиск партнеров

-
-
- {/* Основной контент с табами */}
diff --git a/src/components/messenger/messenger-chat.tsx b/src/components/messenger/messenger-chat.tsx index b6bd1d1..ea33060 100644 --- a/src/components/messenger/messenger-chat.tsx +++ b/src/components/messenger/messenger-chat.tsx @@ -122,6 +122,37 @@ export function MessengerChat({ counterparty }: MessengerChatProps) { return name.charAt(0).toUpperCase() } + const getShortCompanyName = (fullName: string) => { + if (!fullName) return 'Полное название не указано' + + // Словарь для замены полных форм на сокращенные + const legalFormReplacements: { [key: string]: string } = { + 'Общество с ограниченной ответственностью': 'ООО', + 'Открытое акционерное общество': 'ОАО', + 'Закрытое акционерное общество': 'ЗАО', + 'Публичное акционерное общество': 'ПАО', + 'Непубличное акционерное общество': 'НАО', + 'Акционерное общество': 'АО', + 'Индивидуальный предприниматель': 'ИП', + 'Товарищество с ограниченной ответственностью': 'ТОО', + 'Частное предприятие': 'ЧП', + 'Субъект предпринимательской деятельности': 'СПД' + } + + let result = fullName + + // Заменяем полные формы на сокращенные + for (const [fullForm, shortForm] of Object.entries(legalFormReplacements)) { + const regex = new RegExp(`^${fullForm}\\s+`, 'i') + if (regex.test(result)) { + result = result.replace(regex, `${shortForm} `) + break + } + } + + return result + } + const getTypeLabel = (type: string) => { switch (type) { case 'FULFILLMENT': @@ -273,17 +304,17 @@ export function MessengerChat({ counterparty }: MessengerChatProps) {
-

- {getOrganizationName(counterparty)} -

-

- {getManagerName(counterparty)} -

-
+
+

+ {getOrganizationName(counterparty)} +

{getTypeLabel(counterparty.type)}
+

+ {getShortCompanyName(counterparty.fullName || '')} +

diff --git a/src/components/messenger/messenger-conversations.tsx b/src/components/messenger/messenger-conversations.tsx index 3dc6058..9601588 100644 --- a/src/components/messenger/messenger-conversations.tsx +++ b/src/components/messenger/messenger-conversations.tsx @@ -48,6 +48,37 @@ export function MessengerConversations({ return name.charAt(0).toUpperCase() } + const getShortCompanyName = (fullName: string) => { + if (!fullName) return 'Полное название не указано' + + // Словарь для замены полных форм на сокращенные + const legalFormReplacements: { [key: string]: string } = { + 'Общество с ограниченной ответственностью': 'ООО', + 'Открытое акционерное общество': 'ОАО', + 'Закрытое акционерное общество': 'ЗАО', + 'Публичное акционерное общество': 'ПАО', + 'Непубличное акционерное общество': 'НАО', + 'Акционерное общество': 'АО', + 'Индивидуальный предприниматель': 'ИП', + 'Товарищество с ограниченной ответственностью': 'ТОО', + 'Частное предприятие': 'ЧП', + 'Субъект предпринимательской деятельности': 'СПД' + } + + let result = fullName + + // Заменяем полные формы на сокращенные + for (const [fullForm, shortForm] of Object.entries(legalFormReplacements)) { + const regex = new RegExp(`^${fullForm}\\s+`, 'i') + if (regex.test(result)) { + result = result.replace(regex, `${shortForm} `) + break + } + } + + return result + } + const getTypeLabel = (type: string) => { switch (type) { case 'FULFILLMENT': @@ -154,18 +185,17 @@ export function MessengerConversations({
-

- {getOrganizationName(org)} -

- -
- +
+

+ {getOrganizationName(org)} +

+ {getTypeLabel(org.type)}

- {getManagerName(org)} + {getShortCompanyName(org.fullName || '')}

diff --git a/src/components/messenger/messenger-dashboard.tsx b/src/components/messenger/messenger-dashboard.tsx index 5613597..06375eb 100644 --- a/src/components/messenger/messenger-dashboard.tsx +++ b/src/components/messenger/messenger-dashboard.tsx @@ -65,14 +65,6 @@ export function MessengerDashboard() {
- {/* Заголовок - фиксированная высота */} -
-
-

Мессенджер

-

Общение с контрагентами

-
-
- {/* Основной контент - сетка из 2 колонок */}
diff --git a/src/components/services/services-dashboard.tsx b/src/components/services/services-dashboard.tsx index 371d989..1da9e05 100644 --- a/src/components/services/services-dashboard.tsx +++ b/src/components/services/services-dashboard.tsx @@ -12,14 +12,6 @@ export function ServicesDashboard() {
- {/* Заголовок - фиксированная высота */} -
-
-

Услуги

-

Управление услугами, расходниками и логистикой

-
-
- {/* Основной контент с табами */}