fix: Исправление логики загрузки документов и расчёта прогресса

- Исправлена ошибка порядка объявления allDocsProcessed (Cannot access before initialization)
- Исправлена логика поиска незагруженного документа: поиск с начала, если сохранённый индекс уже обработан
- Исправлен расчёт прогресса: теперь используется количество обработанных документов (uploadedDocs + skippedDocs), а не currentDocIndex
- Убрана синхронизация currentDocIndex из formData, которая перезаписывала правильный индекс
- Добавлена логика автоматического пропуска уже загруженных документов при открытии формы
- Добавлено подробное логирование для отладки состояния документов
- Исправлена логика определения завершённости: проверяется каждый документ из documentsRequired

Результат:
- Форма корректно показывает следующий незагруженный документ
- Прогресс правильно отображает процент обработанных документов (75% при 3 из 4)
- Система не требует повторной загрузки уже загруженных документов
This commit is contained in:
AI Assistant
2025-11-27 14:36:42 +03:00
parent 02689e65db
commit 64385c430d

View File

@@ -1383,18 +1383,191 @@ export default function StepWizardPlan({
}); });
// Состояние для поэкранной загрузки документов (новый флоу) // Состояние для поэкранной загрузки документов (новый флоу)
const [currentDocIndex, setCurrentDocIndex] = useState(formData.current_doc_index || 0);
// Убираем дубликаты при инициализации // Убираем дубликаты при инициализации
const initialUploadedDocs = formData.documents_uploaded?.map((d: any) => d.type || d.id) || []; const initialUploadedDocs = formData.documents_uploaded?.map((d: any) => d.type || d.id) || [];
const [uploadedDocs, setUploadedDocs] = useState<string[]>(Array.from(new Set(initialUploadedDocs))); const [uploadedDocs, setUploadedDocs] = useState<string[]>(Array.from(new Set(initialUploadedDocs)));
const [skippedDocs, setSkippedDocs] = useState<string[]>(formData.documents_skipped || []); const [skippedDocs, setSkippedDocs] = useState<string[]>(formData.documents_skipped || []);
// Отладка: логируем инициализацию
useEffect(() => {
console.log('🔍 Инициализация документов:', {
documentsRequiredCount: documentsRequired.length,
initialUploadedDocs,
uploadedDocs,
skippedDocs,
formDataCurrentDocIndex: formData.current_doc_index,
});
}, []); // Только при первой загрузке
// Находим первый незагруженный документ при инициализации
const findFirstUnprocessedDoc = useCallback((startIndex: number = 0) => {
for (let i = startIndex; i < documentsRequired.length; i++) {
const doc = documentsRequired[i];
const docId = doc.id || doc.name;
if (!uploadedDocs.includes(docId) && !skippedDocs.includes(docId)) {
return i;
}
}
return documentsRequired.length; // Все документы обработаны
}, [documentsRequired, uploadedDocs, skippedDocs]);
const [currentDocIndex, setCurrentDocIndex] = useState(() => {
const savedIndex = formData.current_doc_index || 0;
// Используем initialUploadedDocs и formData.documents_skipped для инициализации
const initUploaded = Array.from(new Set(initialUploadedDocs));
const initSkipped = formData.documents_skipped || [];
// Находим первый незагруженный документ
// Сначала проверяем с сохранённого индекса, потом с начала
let firstUnprocessed = documentsRequired.length; // По умолчанию - все обработаны
// Проверяем с сохранённого индекса до конца
for (let i = savedIndex; i < documentsRequired.length; i++) {
const doc = documentsRequired[i];
const docId = doc.id || doc.name;
if (!initUploaded.includes(docId) && !initSkipped.includes(docId)) {
firstUnprocessed = i;
break;
}
}
// Если не нашли с сохранённого индекса, проверяем с начала до сохранённого
if (firstUnprocessed === documentsRequired.length) {
for (let i = 0; i < savedIndex; i++) {
const doc = documentsRequired[i];
const docId = doc.id || doc.name;
if (!initUploaded.includes(docId) && !initSkipped.includes(docId)) {
firstUnprocessed = i;
break;
}
}
}
console.log('🔍 Инициализация currentDocIndex:', {
savedIndex,
firstUnprocessed,
documentsRequiredLength: documentsRequired.length,
initUploaded,
initSkipped,
documentsRequiredList: documentsRequired.map((d: any) => ({
id: d.id || d.name,
name: d.name,
uploaded: initUploaded.includes(d.id || d.name),
skipped: initSkipped.includes(d.id || d.name),
})),
});
// Убеждаемся, что индекс не выходит за границы
return Math.min(firstUnprocessed, documentsRequired.length);
});
// Исправляем currentDocIndex только если он выходит за границы или указывает на уже обработанный документ
useEffect(() => {
if (documentsRequired.length === 0) return;
// Если текущий индекс выходит за границы, исправляем
if (currentDocIndex >= documentsRequired.length) {
const firstUnprocessed = findFirstUnprocessedDoc(0);
console.log('🔄 Исправление currentDocIndex (выход за границы):', {
currentIndex: currentDocIndex,
documentsRequiredLength: documentsRequired.length,
firstUnprocessed,
});
setCurrentDocIndex(firstUnprocessed);
updateFormData({ current_doc_index: firstUnprocessed });
return;
}
// Если текущий документ уже обработан, переходим к следующему
const currentDoc = documentsRequired[currentDocIndex];
if (currentDoc) {
const docId = currentDoc.id || currentDoc.name;
if (uploadedDocs.includes(docId) || skippedDocs.includes(docId)) {
const firstUnprocessed = findFirstUnprocessedDoc(currentDocIndex + 1);
console.log('🔄 Исправление currentDocIndex (документ уже обработан):', {
currentIndex: currentDocIndex,
currentDocId: docId,
firstUnprocessed,
});
setCurrentDocIndex(firstUnprocessed);
updateFormData({ current_doc_index: firstUnprocessed });
}
}
}, [currentDocIndex, documentsRequired.length, uploadedDocs, skippedDocs, findFirstUnprocessedDoc, updateFormData]);
const [docChoice, setDocChoice] = useState<'upload' | 'none'>('upload'); // Выбор: загрузить или нет документа (по умолчанию - загрузить) const [docChoice, setDocChoice] = useState<'upload' | 'none'>('upload'); // Выбор: загрузить или нет документа (по умолчанию - загрузить)
const [currentUploadedFiles, setCurrentUploadedFiles] = useState<any[]>([]); // Массив загруженных файлов const [currentUploadedFiles, setCurrentUploadedFiles] = useState<any[]>([]); // Массив загруженных файлов
// Текущий документ для загрузки // Текущий документ для загрузки
const currentDoc = documentsRequired[currentDocIndex]; const currentDoc = documentsRequired[currentDocIndex];
const isLastDoc = currentDocIndex >= documentsRequired.length - 1; const isLastDoc = currentDocIndex >= documentsRequired.length - 1;
const allDocsProcessed = currentDocIndex >= documentsRequired.length;
// Проверяем, что ВСЕ документы либо загружены, либо пропущены
const allDocsProcessed = useMemo(() => {
// Проверяем каждый документ из documentsRequired
const allRequiredDocsProcessed = documentsRequired.every((doc: any) => {
const docId = doc.id || doc.name;
return uploadedDocs.includes(docId) || skippedDocs.includes(docId);
});
const processedCount = uploadedDocs.length + skippedDocs.length;
const allProcessed = allRequiredDocsProcessed && processedCount >= documentsRequired.length;
console.log('🔍 Проверка завершённости:', {
uploadedDocs: uploadedDocs.length,
skippedDocs: skippedDocs.length,
totalRequired: documentsRequired.length,
processedCount,
allRequiredDocsProcessed,
allProcessed,
uploadedDocsList: uploadedDocs,
skippedDocsList: skippedDocs,
requiredDocsList: documentsRequired.map((d: any) => {
const docId = d.id || d.name;
return {
id: docId,
name: d.name,
uploaded: uploadedDocs.includes(docId),
skipped: skippedDocs.includes(docId),
};
}),
});
return allProcessed;
}, [uploadedDocs, skippedDocs, documentsRequired]);
// Отладка: логируем состояние текущего документа
useEffect(() => {
console.log('🔍 Текущий документ для загрузки:', {
currentDocIndex,
documentsRequiredLength: documentsRequired.length,
currentDoc: currentDoc ? { id: currentDoc.id, name: currentDoc.name } : null,
uploadedDocs,
skippedDocs,
allDocsProcessed,
});
}, [currentDocIndex, documentsRequired.length, currentDoc, uploadedDocs, skippedDocs, allDocsProcessed]);
// Автоматически пропускаем уже загруженные документы
useEffect(() => {
if (!currentDoc || allDocsProcessed) return;
const docId = currentDoc.id || currentDoc.name;
const isAlreadyUploaded = uploadedDocs.includes(docId);
const isAlreadySkipped = skippedDocs.includes(docId);
if (isAlreadyUploaded || isAlreadySkipped) {
console.log(`⏭️ Документ "${currentDoc.name}" уже обработан, переходим к следующему`);
const nextIndex = findFirstUnprocessedDoc(currentDocIndex + 1);
// Обновляем только если следующий индекс отличается от текущего
if (nextIndex !== currentDocIndex) {
setCurrentDocIndex(nextIndex);
updateFormData({
current_doc_index: nextIndex,
});
}
}
}, [currentDoc, uploadedDocs, skippedDocs, currentDocIndex, allDocsProcessed, findFirstUnprocessedDoc, updateFormData]);
// Обработчик выбора файлов (НЕ отправляем сразу, только сохраняем) // Обработчик выбора файлов (НЕ отправляем сразу, только сохраняем)
const handleFilesChange = (fileList: any[]) => { const handleFilesChange = (fileList: any[]) => {
@@ -1418,13 +1591,26 @@ export default function StepWizardPlan({
const newSkipped = [...skippedDocs, currentDoc.id]; const newSkipped = [...skippedDocs, currentDoc.id];
setSkippedDocs(newSkipped); setSkippedDocs(newSkipped);
// Находим следующий незагруженный документ (используем обновлённый список)
const findNextUnprocessed = (startIndex: number) => {
for (let i = startIndex; i < documentsRequired.length; i++) {
const doc = documentsRequired[i];
const docId = doc.id || doc.name;
if (!uploadedDocs.includes(docId) && !newSkipped.includes(docId)) {
return i;
}
}
return documentsRequired.length;
};
const nextIndex = findNextUnprocessed(currentDocIndex + 1);
updateFormData({ updateFormData({
documents_skipped: newSkipped, documents_skipped: newSkipped,
current_doc_index: currentDocIndex + 1, current_doc_index: nextIndex,
}); });
// Переход к следующему (сброс состояния в useEffect) // Переход к следующему незагруженному документу
setCurrentDocIndex(prev => prev + 1); setCurrentDocIndex(nextIndex);
return; return;
} }
@@ -1488,13 +1674,26 @@ export default function StepWizardPlan({
: [...uploadedDocs, currentDoc.id]; : [...uploadedDocs, currentDoc.id];
setUploadedDocs(newUploaded); setUploadedDocs(newUploaded);
// Находим следующий незагруженный документ (используем обновлённый список)
const findNextUnprocessed = (startIndex: number) => {
for (let i = startIndex; i < documentsRequired.length; i++) {
const doc = documentsRequired[i];
const docId = doc.id || doc.name;
if (!newUploaded.includes(docId) && !skippedDocs.includes(docId)) {
return i;
}
}
return documentsRequired.length;
};
const nextIndex = findNextUnprocessed(currentDocIndex + 1);
updateFormData({ updateFormData({
documents_uploaded: uploadedDocsData, documents_uploaded: uploadedDocsData,
current_doc_index: currentDocIndex + 1, current_doc_index: nextIndex,
}); });
// Переход к следующему (сброс состояния в useEffect) // Переход к следующему незагруженному документу
setCurrentDocIndex(prev => prev + 1); setCurrentDocIndex(nextIndex);
} catch (error: any) { } catch (error: any) {
message.error(`Ошибка загрузки: ${error.message}`); message.error(`Ошибка загрузки: ${error.message}`);
@@ -1540,16 +1739,16 @@ export default function StepWizardPlan({
}} }}
> >
{/* ✅ НОВЫЙ ФЛОУ: Поэкранная загрузка документов */} {/* ✅ НОВЫЙ ФЛОУ: Поэкранная загрузка документов */}
{hasNewFlowDocs && !allDocsProcessed && currentDoc && ( {hasNewFlowDocs && !allDocsProcessed && currentDocIndex < documentsRequired.length && currentDoc ? (
<div style={{ padding: '24px 0' }}> <div style={{ padding: '24px 0' }}>
{/* Прогресс */} {/* Прогресс */}
<div style={{ marginBottom: 24 }}> <div style={{ marginBottom: 24 }}>
<div style={{ display: 'flex', justifyContent: 'space-between', marginBottom: 8 }}> <div style={{ display: 'flex', justifyContent: 'space-between', marginBottom: 8 }}>
<Text type="secondary">Документ {currentDocIndex + 1} из {documentsRequired.length}</Text> <Text type="secondary">Документ {currentDocIndex + 1} из {documentsRequired.length}</Text>
<Text type="secondary">{Math.round((currentDocIndex / documentsRequired.length) * 100)}% завершено</Text> <Text type="secondary">{Math.round(((uploadedDocs.length + skippedDocs.length) / documentsRequired.length) * 100)}% завершено</Text>
</div> </div>
<Progress <Progress
percent={Math.round((currentDocIndex / documentsRequired.length) * 100)} percent={Math.round(((uploadedDocs.length + skippedDocs.length) / documentsRequired.length) * 100)}
showInfo={false} showInfo={false}
strokeColor="#595959" strokeColor="#595959"
/> />
@@ -1659,20 +1858,52 @@ export default function StepWizardPlan({
</div> </div>
)} )}
</div> </div>
)} ) : hasNewFlowDocs && !allDocsProcessed && currentDocIndex >= documentsRequired.length ? (
<div style={{ padding: '24px 0', textAlign: 'center' }}>
<Text type="warning">
Ошибка: индекс документа ({currentDocIndex}) выходит за границы массива ({documentsRequired.length}).
<br />
Загружено: {uploadedDocs.length}, пропущено: {skippedDocs.length}
</Text>
<Button
type="primary"
style={{ marginTop: 16 }}
onClick={() => {
const nextIndex = findFirstUnprocessedDoc(0);
setCurrentDocIndex(nextIndex);
updateFormData({ current_doc_index: nextIndex });
}}
>
Исправить и продолжить
</Button>
</div>
) : null}
{/* ✅ НОВЫЙ ФЛОУ: Все документы загружены */} {/* ✅ НОВЫЙ ФЛОУ: Все документы загружены */}
{hasNewFlowDocs && allDocsProcessed && ( {hasNewFlowDocs && allDocsProcessed && (() => {
// Правильно считаем загруженные и пропущенные документы из documentsRequired
const uploadedCount = documentsRequired.filter((doc: any) => {
const docId = doc.id || doc.name;
return uploadedDocs.includes(docId);
}).length;
const skippedCount = documentsRequired.filter((doc: any) => {
const docId = doc.id || doc.name;
return skippedDocs.includes(docId);
}).length;
return (
<div style={{ textAlign: 'center', padding: '40px 0' }}> <div style={{ textAlign: 'center', padding: '40px 0' }}>
<Title level={4}> Все документы загружены!</Title> <Title level={4}> Все документы обработаны!</Title>
<Paragraph type="secondary"> <Paragraph type="secondary">
Загружено: {uploadedDocs.length}, пропущено: {skippedDocs.length} Загружено: {uploadedCount} из {documentsRequired.length}, пропущено: {skippedCount}
</Paragraph> </Paragraph>
<Button type="primary" size="large" onClick={handleAllDocsComplete}> <Button type="primary" size="large" onClick={handleAllDocsComplete}>
Продолжить Продолжить
</Button> </Button>
</div> </div>
)} );
})()}
{/* СТАРЫЙ ФЛОУ: Ожидание визарда */} {/* СТАРЫЙ ФЛОУ: Ожидание визарда */}
{!hasNewFlowDocs && isWaiting && ( {!hasNewFlowDocs && isWaiting && (