diff --git a/FRONTEND_API_CHANGES.md b/FRONTEND_API_CHANGES.md new file mode 100644 index 0000000..56f923a --- /dev/null +++ b/FRONTEND_API_CHANGES.md @@ -0,0 +1,1028 @@ +# Документация изменений API для фронтенда + +## Обзор изменений + +В API маркетингового анализа были внесены изменения для поддержки множественного выбора в трех полях: + +1. **Целевая аудитория** (`targetAudience`) - теперь структурированный JSON объект вместо строки +2. **Тип анализа** (`analysisType`) - теперь массив строк вместо одной строки +3. **Регион** (`region`) - теперь массив строк вместо одной строки + +**Важно**: При выборе нескольких типов анализа API создает отдельные записи анализа для каждого типа. В ответе возвращается массив анализов. + +--- + +## 1. Изменения в поле `targetAudience` + +### Старый формат (устарел) + +```typescript +targetAudience: string; +``` + +```json +"targetAudience": "Малый и средний бизнес, владельцы интернет-магазинов в возрасте 30-50 лет" +``` + +### Новый формат + +```typescript +targetAudience: { + genders?: ('Женщины' | 'Мужчины')[]; + ageRanges?: ('20-40' | '25-45' | '18-25' | '40-60' | '60+')[]; + types?: ('Семьи' | 'Молодёжь' | 'Все подряд')[]; +} +``` + +### Примеры + +**Минимальный выбор (только гендер):** + +```json +{ + "targetAudience": { + "genders": ["Женщины"] + } +} +``` + +**Полный выбор:** + +```json +{ + "targetAudience": { + "genders": ["Женщины", "Мужчины"], + "ageRanges": ["20-40", "25-45"], + "types": ["Семьи", "Молодёжь"] + } +} +``` + +**Только возраст и тип:** + +```json +{ + "targetAudience": { + "ageRanges": ["25-45"], + "types": ["Молодёжь"] + } +} +``` + +### Валидация + +- Хотя бы одно поле (`genders`, `ageRanges`, `types`) должно быть заполнено +- Каждое поле должно содержать непустой массив +- Все значения должны быть из допустимого списка + +### Допустимые значения + +- **genders**: `"Женщины"`, `"Мужчины"` +- **ageRanges**: `"20-40"`, `"25-45"`, `"18-25"`, `"40-60"`, `"60+"` +- **types**: `"Семьи"`, `"Молодёжь"`, `"Все подряд"` + +--- + +## 2. Изменения в поле `analysisType` + +### Старый формат (устарел) + +```typescript +analysisType: 'РЫНОК' | 'КОНКУРЕНТЫ' | 'ЦА' | 'КАНАЛЫ' | 'SWOT'; +``` + +```json +"analysisType": "РЫНОК" +``` + +### Новый формат + +```typescript +analysisType: ('РЫНОК' | 'КОНКУРЕНТЫ' | 'ЦА' | 'КАНАЛЫ' | 'SWOT')[] +``` + +### Примеры + +**Один тип анализа:** + +```json +{ + "analysisType": ["РЫНОК"] +} +``` + +**Несколько типов анализа:** + +```json +{ + "analysisType": ["РЫНОК", "КОНКУРЕНТЫ", "ЦА"] +} +``` + +**Все типы анализа:** + +```json +{ + "analysisType": ["РЫНОК", "КОНКУРЕНТЫ", "ЦА", "КАНАЛЫ", "SWOT"] +} +``` + +### Важно: Поведение при множественном выборе + +При выборе нескольких типов анализа: + +- API создает **отдельную запись анализа** для каждого типа +- Каждый анализ обрабатывается независимо +- В ответе возвращается **массив** с информацией о каждом созданном анализе +- Каждый анализ имеет свой `analysisId` и статус + +### Валидация + +- Массив должен содержать минимум один элемент +- Все значения должны быть из допустимого списка + +### Допустимые значения + +- `"РЫНОК"` - Анализ рынка +- `"КОНКУРЕНТЫ"` - Анализ конкурентов +- `"ЦА"` - Анализ целевой аудитории +- `"КАНАЛЫ"` - Анализ маркетинговых каналов +- `"SWOT"` - SWOT-анализ + +--- + +## 3. Изменения в поле `region` + +### Старый формат (устарел) + +```typescript +region: string; +``` + +```json +"region": "Алматы" +``` + +### Новый формат + +```typescript +region: string[] +``` + +### Примеры + +**Один регион:** + +```json +{ + "region": ["Алматы"] +} +``` + +**Несколько регионов:** + +```json +{ + "region": ["Алматы", "Астана", "Шымкент"] +} +``` + +**Все регионы:** + +```json +{ + "region": [ + "Алматы", + "Астана", + "Шымкент", + "Караганда", + "Актобе", + "Тараз", + "Павлодар", + "Усть-Каменогорск", + "Семей", + "Костанай", + "Кызылорда", + "Уральск", + "Петропавловск", + "Атырау", + "Актау", + "Туркестан", + "Кокшетау", + "Талдыкорган", + "Экибастуз", + "Рудный" + ] +} +``` + +### Валидация + +- Массив должен содержать минимум один элемент +- Все значения должны быть из допустимого списка городов Казахстана + +### Допустимые значения + +Всего 20 городов: + +- `"Алматы"`, `"Астана"`, `"Шымкент"`, `"Караганда"`, `"Актобе"`, `"Тараз"`, `"Павлодар"`, `"Усть-Каменогорск"`, `"Семей"`, `"Костанай"`, `"Кызылорда"`, `"Уральск"`, `"Петропавловск"`, `"Атырау"`, `"Актау"`, `"Туркестан"`, `"Кокшетау"`, `"Талдыкорган"`, `"Экибастуз"`, `"Рудный"` + +--- + +## 4. Изменения в ответе API + +### Эндпоинт: `POST /api/marketing/analysis/start` + +### Старый формат ответа (один анализ) + +```json +{ + "success": true, + "message": "Анализ запущен успешно", + "data": { + "analysisId": "507f1f77bcf86cd799439011", + "status": "processing", + "estimatedCompletion": "2024-01-15T10:30:00", + "message": "Анализ запущен успешно. Результаты будут готовы в течение 5-10 минут." + } +} +``` + +### Новый формат ответа + +**Если выбран один тип анализа:** + +```json +{ + "success": true, + "message": "Анализ запущен успешно", + "data": { + "analysisId": "507f1f77bcf86cd799439011", + "status": "processing", + "estimatedCompletion": "2024-01-15T10:30:00", + "message": "Анализ запущен успешно. Результаты будут готовы в течение 5-10 минут." + } +} +``` + +**Если выбрано несколько типов анализа:** + +```json +{ + "success": true, + "message": "Создано анализов: 3. Все анализы запущены успешно.", + "data": [ + { + "analysisId": "507f1f77bcf86cd799439011", + "status": "processing", + "estimatedCompletion": "2024-01-15T10:30:00", + "message": "Анализ типа 'РЫНОК' запущен успешно. Результаты будут готовы в течение 5-10 минут." + }, + { + "analysisId": "507f1f77bcf86cd799439012", + "status": "processing", + "estimatedCompletion": "2024-01-15T10:30:00", + "message": "Анализ типа 'КОНКУРЕНТЫ' запущен успешно. Результаты будут готовы в течение 5-10 минут." + }, + { + "analysisId": "507f1f77bcf86cd799439013", + "status": "processing", + "estimatedCompletion": "2024-01-15T10:30:00", + "message": "Анализ типа 'ЦА' запущен успешно. Результаты будут готовы в течение 5-10 минут." + } + ] +} +``` + +--- + +## 5. Полный пример запроса + +### Пример 1: Один тип анализа + +```json +{ + "businessNiche": "E-commerce платформы", + "product": "Разработка мобильных приложений", + "targetAudience": { + "genders": ["Женщины"], + "ageRanges": ["25-45"], + "types": ["Молодёжь"] + }, + "region": ["Алматы"], + "goal": "Увеличить количество клиентов на 50% за следующие 6 месяцев", + "detailLevel": "СТАНДАРТНО", + "strongSide": "Опытная команда, быстрая доставка", + "weakSide": "Ограниченный маркетинговый бюджет", + "analysisType": ["РЫНОК"] +} +``` + +### Пример 2: Несколько типов анализа + +```json +{ + "businessNiche": "E-commerce платформы", + "product": "Разработка мобильных приложений", + "targetAudience": { + "genders": ["Женщины", "Мужчины"], + "ageRanges": ["25-45", "40-60"], + "types": ["Семьи", "Молодёжь"] + }, + "region": ["Алматы", "Астана", "Шымкент"], + "goal": "Увеличить количество клиентов на 50% за следующие 6 месяцев", + "detailLevel": "СТАНДАРТНО", + "strongSide": "Опытная команда, быстрая доставка", + "weakSide": "Ограниченный маркетинговый бюджет", + "analysisType": ["РЫНОК", "КОНКУРЕНТЫ", "ЦА"] +} +``` + +--- + +## 6. TypeScript интерфейсы + +```typescript +// Интерфейс для целевой аудитории +interface TargetAudience { + genders?: ('Женщины' | 'Мужчины')[]; + ageRanges?: ('20-40' | '25-45' | '18-25' | '40-60' | '60+')[]; + types?: ('Семьи' | 'Молодёжь' | 'Все подряд')[]; +} + +// Интерфейс запроса +interface MarketingAnalysisRequest { + businessNiche: string; + product: string; + targetAudience: TargetAudience; + region: string[]; + goal: string; + detailLevel: 'КРАТКО' | 'СТАНДАРТНО' | 'ПОДРОБНО'; + strongSide?: string; + weakSide?: string; + analysisType: ('РЫНОК' | 'КОНКУРЕНТЫ' | 'ЦА' | 'КАНАЛЫ' | 'SWOT')[]; +} + +// Интерфейс ответа +interface MarketingAnalysisResponse { + analysisId: string; + status: string; + estimatedCompletion: string; + message: string; +} + +// Тип ответа API (может быть один объект или массив) +type StartAnalysisResponse = + | MarketingAnalysisResponse + | MarketingAnalysisResponse[]; +``` + +--- + +## 7. Примеры реализации на фронтенде + +### React компонент с формой + +```tsx +import React, { useState } from 'react'; + +const VALID_GENDERS = ['Женщины', 'Мужчины'] as const; +const VALID_AGE_RANGES = ['20-40', '25-45', '18-25', '40-60', '60+'] as const; +const VALID_TYPES = ['Семьи', 'Молодёжь', 'Все подряд'] as const; +const VALID_ANALYSIS_TYPES = [ + 'РЫНОК', + 'КОНКУРЕНТЫ', + 'ЦА', + 'КАНАЛЫ', + 'SWOT', +] as const; +const VALID_REGIONS = [ + 'Алматы', + 'Астана', + 'Шымкент', + 'Караганда', + 'Актобе', + 'Тараз', + 'Павлодар', + 'Усть-Каменогорск', + 'Семей', + 'Костанай', + 'Кызылорда', + 'Уральск', + 'Петропавловск', + 'Атырау', + 'Актау', + 'Туркестан', + 'Кокшетау', + 'Талдыкорган', + 'Экибастуз', + 'Рудный', +] as const; + +const MarketingAnalysisForm: React.FC = () => { + const [formData, setFormData] = useState({ + businessNiche: '', + product: '', + targetAudience: { + genders: [], + ageRanges: [], + types: [], + }, + region: [], + goal: '', + detailLevel: 'СТАНДАРТНО', + strongSide: '', + weakSide: '', + analysisType: [], + }); + + const handleSubmit = async (e: React.FormEvent) => { + e.preventDefault(); + + // Валидация: проверяем, что выбрана хотя бы одна опция в каждом обязательном поле + if ( + formData.targetAudience.genders?.length === 0 && + formData.targetAudience.ageRanges?.length === 0 && + formData.targetAudience.types?.length === 0 + ) { + alert('Выберите хотя бы одну опцию для целевой аудитории'); + return; + } + + if (formData.region.length === 0) { + alert('Выберите хотя бы один регион'); + return; + } + + if (formData.analysisType.length === 0) { + alert('Выберите хотя бы один тип анализа'); + return; + } + + try { + const response = await fetch('/api/marketing/analysis/start', { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + Authorization: `Bearer ${getAuthToken()}`, + }, + body: JSON.stringify(formData), + }); + + const result = await response.json(); + + if (!result.success) { + throw new Error(result.error?.message || 'Ошибка при создании анализа'); + } + + // Обработка ответа (может быть массив или один объект) + if (Array.isArray(result.data)) { + console.log(`Создано ${result.data.length} анализов`); + result.data.forEach( + (analysis: MarketingAnalysisResponse, index: number) => { + console.log(`Анализ ${index + 1}: ${analysis.analysisId}`); + // Перенаправление на страницу с результатами для каждого анализа + } + ); + } else { + console.log('Анализ создан:', result.data.analysisId); + // Перенаправление на страницу с результатами + } + } catch (error) { + console.error('Ошибка:', error); + alert('Не удалось создать анализ. Попробуйте позже.'); + } + }; + + return ( +
+ {/* Поле: Целевая аудитория */} +
+ + +
+ + {VALID_GENDERS.map((gender) => ( + + ))} +
+ +
+ + {VALID_AGE_RANGES.map((age) => ( + + ))} +
+ +
+ + {VALID_TYPES.map((type) => ( + + ))} +
+ + {/* Отображение выбранных значений */} +
+ Выбрано: + {formData.targetAudience.genders && + formData.targetAudience.genders.length > 0 && ( + Гендер: {formData.targetAudience.genders.join(', ')} + )} + {formData.targetAudience.ageRanges && + formData.targetAudience.ageRanges.length > 0 && ( + + {' '} + Возраст: {formData.targetAudience.ageRanges.join(', ')} + + )} + {formData.targetAudience.types && + formData.targetAudience.types.length > 0 && ( + Тип: {formData.targetAudience.types.join(', ')} + )} +
+
+ + {/* Поле: Регион */} +
+ +
+ +
+
+ {VALID_REGIONS.map((region) => ( + + ))} +
+ {/* Отображение выбранных регионов */} +
+ Выбрано регионов: {formData.region.length} + {formData.region.length > 0 && ( +
{formData.region.join(', ')}
+ )} +
+
+ + {/* Поле: Тип анализа */} +
+ +
+ +
+
+ {VALID_ANALYSIS_TYPES.map((type) => ( + + ))} +
+ {/* Отображение выбранных типов */} +
+ Выбрано типов: {formData.analysisType.length} + {formData.analysisType.length > 0 && ( +
{formData.analysisType.join(', ')}
+ )} +
+
+ + {/* Остальные поля формы */} + {/* ... */} + + +
+ ); +}; + +export default MarketingAnalysisForm; +``` + +--- + +## 8. Обработка ответа API + +```typescript +async function handleAnalysisResponse(response: any) { + if (!response.success) { + throw new Error(response.error?.message || 'Ошибка при создании анализа'); + } + + const data = response.data; + + // Проверяем, является ли ответ массивом + if (Array.isArray(data)) { + // Множественные анализы + console.log(`Создано ${data.length} анализов`); + + // Можно показать уведомление пользователю + showNotification( + `Создано ${data.length} анализов. Они будут обработаны параллельно.` + ); + + // Сохраняем все ID анализов + const analysisIds = data.map( + (item: MarketingAnalysisResponse) => item.analysisId + ); + + // Можно перенаправить на страницу со списком анализов + navigate(`/analyses?ids=${analysisIds.join(',')}`); + + // Или создать задачи для отслеживания статуса каждого анализа + data.forEach((analysis: MarketingAnalysisResponse) => { + startPollingAnalysisStatus(analysis.analysisId); + }); + } else { + // Один анализ + console.log('Анализ создан:', data.analysisId); + + // Перенаправление на страницу с результатами + navigate(`/analysis/${data.analysisId}`); + + // Запуск отслеживания статуса + startPollingAnalysisStatus(data.analysisId); + } +} +``` + +--- + +## 9. Валидация на фронтенде + +```typescript +function validateMarketingAnalysisRequest(data: MarketingAnalysisRequest): { + isValid: boolean; + errors: string[]; +} { + const errors: string[] = []; + + // Валидация целевой аудитории + const hasGenders = + data.targetAudience.genders && data.targetAudience.genders.length > 0; + const hasAgeRanges = + data.targetAudience.ageRanges && data.targetAudience.ageRanges.length > 0; + const hasTypes = + data.targetAudience.types && data.targetAudience.types.length > 0; + + if (!hasGenders && !hasAgeRanges && !hasTypes) { + errors.push( + 'Выберите хотя бы одну опцию для целевой аудитории (гендер, возраст или тип)' + ); + } + + // Валидация регионов + if (!data.region || data.region.length === 0) { + errors.push('Выберите хотя бы один регион'); + } + + // Валидация типов анализа + if (!data.analysisType || data.analysisType.length === 0) { + errors.push('Выберите хотя бы один тип анализа'); + } + + // Валидация других полей + if (!data.businessNiche || data.businessNiche.trim().length < 3) { + errors.push('Ниша бизнеса должна содержать минимум 3 символа'); + } + + if (!data.product || data.product.trim().length < 3) { + errors.push('Продукт/услуга должна содержать минимум 3 символа'); + } + + if (!data.goal || data.goal.trim().length < 10) { + errors.push('Цель должна содержать минимум 10 символов'); + } + + return { + isValid: errors.length === 0, + errors, + }; +} +``` + +--- + +## 10. Миграция существующего кода + +### Шаг 1: Обновить TypeScript интерфейсы + +Замените старые интерфейсы на новые: + +```typescript +// Старый код +interface MarketingAnalysisRequest { + targetAudience: string; + region: string; + analysisType: string; +} + +// Новый код +interface MarketingAnalysisRequest { + targetAudience: TargetAudience; + region: string[]; + analysisType: string[]; +} +``` + +### Шаг 2: Обновить компоненты формы + +- Замените `