feat: Auto-navigate to confirmation form when draft is fully filled
Problem:
- When user selects a draft with all steps filled (description, plan, answers, documents)
- But claim status is still 'draft' (not submitted)
- User has to manually navigate through all steps again
Solution:
1. Added check in loadDraft() to detect fully filled drafts:
- hasDescription: problem_description exists
- hasWizardPlan: wizard_plan exists
- hasAnswers: answers exist and not empty
- hasDocuments: documents_meta array has items
- isDraft: status_code === 'draft'
- allStepsFilled: all checks pass
2. When draft is ready for confirmation:
- Automatically subscribe to claim:plan SSE channel
- Wait for claim data from n8n
- Show loading message while waiting
- On success: show confirmation form automatically
3. Added subscribeToClaimPlanForDraft() function:
- Subscribes to /api/v1/claim-plan/{session_token}
- Handles claim_plan_ready event
- Updates formData with claimPlanData
- Auto-navigates to confirmation step via useEffect
4. Added useEffect for auto-navigation:
- Watches formData.showClaimConfirmation and formData.claimPlanData
- When both true, navigates to step 3 (confirmation)
- Handles cleanup of EventSource on unmount
Flow:
1. User selects draft → loadDraft() checks completeness
2. If all filled + draft → subscribeToClaimPlanForDraft()
3. SSE receives data → updates formData
4. useEffect detects → navigates to confirmation step
5. User sees confirmation form immediately
Files:
- frontend/src/pages/ClaimForm.tsx: Added auto-navigation logic
This commit is contained in:
@@ -76,6 +76,8 @@ export default function ClaimForm() {
|
||||
// session_id будет получен от n8n при создании контакта
|
||||
// Используем useRef чтобы sessionId не вызывал перерендер и был стабильным
|
||||
const sessionIdRef = useRef(`sess-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`);
|
||||
const claimPlanEventSourceRef = useRef<EventSource | null>(null);
|
||||
const claimPlanTimeoutRef = useRef<NodeJS.Timeout | null>(null);
|
||||
|
||||
const [currentStep, setCurrentStep] = useState(0);
|
||||
const [sessionRestored, setSessionRestored] = useState(false); // Флаг: пытались восстановить сессию
|
||||
@@ -209,6 +211,39 @@ export default function ClaimForm() {
|
||||
fetchClientIp();
|
||||
}, []);
|
||||
|
||||
// Автоматический переход к шагу подтверждения, когда данные готовы
|
||||
useEffect(() => {
|
||||
if (formData.showClaimConfirmation && formData.claimPlanData) {
|
||||
// Вычисляем индекс шага подтверждения динамически
|
||||
// Шаг подтверждения добавляется после StepWizardPlan
|
||||
// После выбора черновика showDraftSelection = false, поэтому:
|
||||
// - Шаг 0 = Step1Phone
|
||||
// - Шаг 1 = StepDescription
|
||||
// - Шаг 2 = StepWizardPlan
|
||||
// - Шаг 3 = StepClaimConfirmation (если showClaimConfirmation=true)
|
||||
const confirmationStepIndex = 3; // Фиксированный индекс для шага подтверждения
|
||||
|
||||
console.log('✅ Данные заявления готовы, переходим к шагу подтверждения:', confirmationStepIndex);
|
||||
setTimeout(() => {
|
||||
setCurrentStep(confirmationStepIndex);
|
||||
}, 100);
|
||||
}
|
||||
}, [formData.showClaimConfirmation, formData.claimPlanData]);
|
||||
|
||||
// Cleanup: закрываем SSE соединение при размонтировании
|
||||
useEffect(() => {
|
||||
return () => {
|
||||
if (claimPlanEventSourceRef.current) {
|
||||
claimPlanEventSourceRef.current.close();
|
||||
claimPlanEventSourceRef.current = null;
|
||||
}
|
||||
if (claimPlanTimeoutRef.current) {
|
||||
clearTimeout(claimPlanTimeoutRef.current);
|
||||
claimPlanTimeoutRef.current = null;
|
||||
}
|
||||
};
|
||||
}, []);
|
||||
|
||||
// Динамически определяем список шагов на основе выбранного eventType
|
||||
const documentConfigs = formData.eventType ? getDocumentsForEventType(formData.eventType) : [];
|
||||
const totalDocumentSteps = documentConfigs.length;
|
||||
@@ -249,6 +284,98 @@ export default function ClaimForm() {
|
||||
});
|
||||
}, []);
|
||||
|
||||
// Подписка на канал claim:plan для получения данных заявления (для черновиков)
|
||||
const subscribeToClaimPlanForDraft = useCallback((sessionToken: string, claimId: string) => {
|
||||
console.log('📡 Подписка на канал claim:plan для черновика:', { sessionToken, claimId });
|
||||
|
||||
// Закрываем предыдущее соединение, если есть
|
||||
if (claimPlanEventSourceRef.current) {
|
||||
claimPlanEventSourceRef.current.close();
|
||||
claimPlanEventSourceRef.current = null;
|
||||
}
|
||||
|
||||
// Очищаем предыдущий таймаут
|
||||
if (claimPlanTimeoutRef.current) {
|
||||
clearTimeout(claimPlanTimeoutRef.current);
|
||||
claimPlanTimeoutRef.current = null;
|
||||
}
|
||||
|
||||
// Создаём новое SSE соединение
|
||||
const eventSource = new EventSource(`/api/v1/claim-plan/${sessionToken}`);
|
||||
claimPlanEventSourceRef.current = eventSource;
|
||||
|
||||
eventSource.onopen = () => {
|
||||
console.log('✅ Подключено к каналу claim:plan для черновика');
|
||||
addDebugEvent('claim-plan', 'info', '📡 Ожидание данных заявления для черновика...');
|
||||
message.loading('Загрузка данных заявления...', 0);
|
||||
};
|
||||
|
||||
eventSource.onmessage = (event) => {
|
||||
try {
|
||||
const data = JSON.parse(event.data);
|
||||
console.log('📥 Получены данные из claim:plan для черновика:', data);
|
||||
|
||||
if (data.event_type === 'claim_plan_ready' && data.status === 'ready') {
|
||||
// Данные заявления получены!
|
||||
message.destroy(); // Убираем loading сообщение
|
||||
message.success('Данные заявления загружены!');
|
||||
|
||||
// Сохраняем данные заявления в formData
|
||||
updateFormData({
|
||||
claimPlanData: data.data, // Данные от n8n
|
||||
showClaimConfirmation: true, // Флаг для показа формы подтверждения
|
||||
});
|
||||
|
||||
// Закрываем SSE соединение
|
||||
eventSource.close();
|
||||
claimPlanEventSourceRef.current = null;
|
||||
|
||||
// Переход к шагу подтверждения произойдёт автоматически через useEffect
|
||||
} else if (data.event_type === 'claim_plan_error' || data.status === 'error') {
|
||||
message.destroy();
|
||||
message.error(data.message || 'Ошибка получения данных заявления');
|
||||
eventSource.close();
|
||||
claimPlanEventSourceRef.current = null;
|
||||
// Переходим к визарду вместо подтверждения
|
||||
setCurrentStep(2);
|
||||
} else if (data.event_type === 'claim_plan_timeout' || data.status === 'timeout') {
|
||||
message.destroy();
|
||||
message.warning('Данные заявления ещё обрабатываются. Вы можете продолжить редактирование.');
|
||||
eventSource.close();
|
||||
claimPlanEventSourceRef.current = null;
|
||||
// Переходим к визарду вместо подтверждения
|
||||
setCurrentStep(2);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('❌ Ошибка парсинга данных из claim:plan:', error);
|
||||
message.destroy();
|
||||
message.error('Ошибка обработки данных заявления');
|
||||
eventSource.close();
|
||||
claimPlanEventSourceRef.current = null;
|
||||
setCurrentStep(2);
|
||||
}
|
||||
};
|
||||
|
||||
eventSource.onerror = (error) => {
|
||||
console.error('❌ Ошибка SSE соединения claim:plan:', error);
|
||||
message.destroy();
|
||||
message.warning('Не удалось получить данные заявления. Вы можете продолжить редактирование.');
|
||||
eventSource.close();
|
||||
claimPlanEventSourceRef.current = null;
|
||||
setCurrentStep(2); // Переходим к визарду вместо подтверждения
|
||||
};
|
||||
|
||||
// Таймаут на 5 минут
|
||||
claimPlanTimeoutRef.current = setTimeout(() => {
|
||||
console.warn('⏰ Таймаут ожидания данных заявления для черновика');
|
||||
message.destroy();
|
||||
message.warning('Превышено время ожидания данных заявления. Вы можете продолжить редактирование.');
|
||||
eventSource.close();
|
||||
claimPlanEventSourceRef.current = null;
|
||||
setCurrentStep(2);
|
||||
}, 300000); // 5 минут
|
||||
}, [updateFormData, addDebugEvent]);
|
||||
|
||||
// Загрузка черновика
|
||||
const loadDraft = useCallback(async (claimId: string) => {
|
||||
try {
|
||||
@@ -285,6 +412,7 @@ export default function ClaimForm() {
|
||||
const wizardPlanRaw = body.wizard_plan || payload.wizard_plan;
|
||||
const answersRaw = body.answers || payload.answers;
|
||||
const problemDescription = body.problem_description || payload.problem_description || body.description || payload.description;
|
||||
const documentsMeta = body.documents_meta || payload.documents_meta || [];
|
||||
|
||||
// ✅ Парсим wizard_plan и answers, если они строки (JSON)
|
||||
let wizardPlan = wizardPlanRaw;
|
||||
@@ -305,9 +433,30 @@ export default function ClaimForm() {
|
||||
}
|
||||
}
|
||||
|
||||
// ✅ Проверяем, заполнены ли все шаги
|
||||
const hasDescription = !!problemDescription;
|
||||
const hasWizardPlan = !!wizardPlan;
|
||||
const hasAnswers = !!answers && Object.keys(answers).length > 0;
|
||||
const hasDocuments = Array.isArray(documentsMeta) && documentsMeta.length > 0;
|
||||
const isDraft = claim.status_code === 'draft';
|
||||
|
||||
const allStepsFilled = hasDescription && hasWizardPlan && hasAnswers && hasDocuments;
|
||||
const isReadyForConfirmation = allStepsFilled && isDraft;
|
||||
|
||||
console.log('🔍 Проверка полноты черновика:', {
|
||||
hasDescription,
|
||||
hasWizardPlan,
|
||||
hasAnswers,
|
||||
hasDocuments,
|
||||
isDraft,
|
||||
allStepsFilled,
|
||||
isReadyForConfirmation,
|
||||
});
|
||||
|
||||
console.log('🔍 problem_description:', problemDescription ? 'есть' : 'нет');
|
||||
console.log('🔍 wizard_plan:', wizardPlan ? 'есть' : 'нет');
|
||||
console.log('🔍 answers:', answers ? 'есть' : 'нет');
|
||||
console.log('🔍 documents_meta:', documentsMeta.length, 'документов');
|
||||
console.log('🔍 Все ключи payload:', Object.keys(payload));
|
||||
if (isTelegramFormat) {
|
||||
console.log('🔍 Все ключи body:', Object.keys(body));
|
||||
@@ -354,6 +503,21 @@ export default function ClaimForm() {
|
||||
setSelectedDraftId(finalClaimId);
|
||||
setShowDraftSelection(false);
|
||||
|
||||
// ✅ Если все шаги заполнены и статус = draft → переходим к форме подтверждения
|
||||
if (isReadyForConfirmation) {
|
||||
console.log('✅ Все шаги заполнены, переходим к форме подтверждения');
|
||||
|
||||
setIsPhoneVerified(true);
|
||||
|
||||
// Подписываемся на канал claim:plan для получения данных заявления
|
||||
subscribeToClaimPlanForDraft(actualSessionId, finalClaimId);
|
||||
|
||||
// Пока устанавливаем шаг визарда, переход к подтверждению произойдёт автоматически
|
||||
// когда данные будут получены через SSE
|
||||
setCurrentStep(2); // StepWizardPlan
|
||||
return;
|
||||
}
|
||||
|
||||
// ✅ Определяем шаг для перехода на основе данных черновика
|
||||
// Приоритет: если есть wizard_plan → переходим к визарду (даже если нет problem_description)
|
||||
// После выбора черновика showDraftSelection = false, поэтому:
|
||||
|
||||
Reference in New Issue
Block a user