# Документация изменений 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: Обновить компоненты формы - Замените `