fix: Исправление логики загрузки документов и расчёта прогресса
- Исправлена ошибка порядка объявления allDocsProcessed (Cannot access before initialization) - Исправлена логика поиска незагруженного документа: поиск с начала, если сохранённый индекс уже обработан - Исправлен расчёт прогресса: теперь используется количество обработанных документов (uploadedDocs + skippedDocs), а не currentDocIndex - Убрана синхронизация currentDocIndex из formData, которая перезаписывала правильный индекс - Добавлена логика автоматического пропуска уже загруженных документов при открытии формы - Добавлено подробное логирование для отладки состояния документов - Исправлена логика определения завершённости: проверяется каждый документ из documentsRequired Результат: - Форма корректно показывает следующий незагруженный документ - Прогресс правильно отображает процент обработанных документов (75% при 3 из 4) - Система не требует повторной загрузки уже загруженных документов
This commit is contained in:
@@ -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 && (
|
||||||
|
|||||||
Reference in New Issue
Block a user