From ffc5da75437b0e4b386cafbf849c9b48af23c4e2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=94=D0=BC=D0=B8=D1=82=D1=80=D0=B8=D0=B9?= Date: Sun, 5 Jul 2026 04:31:19 +0300 Subject: [PATCH] =?UTF-8?q?feat(autopodbor):=20=D0=BF=D0=BE=D0=BA=D0=B0?= =?UTF-8?q?=D0=B7=20=D0=BE=D1=87=D0=B5=D1=80=D0=B5=D0=B4=D0=B8=20=D0=B8=20?= =?UTF-8?q?=D1=8D=D1=82=D0=B0=D0=BF=D0=B0=20=D0=BA=D0=BB=D0=B8=D0=B5=D0=BD?= =?UTF-8?q?=D1=82=D1=83=20=D0=B2=D0=BE=20=D0=B2=D1=80=D0=B5=D0=BC=D1=8F=20?= =?UTF-8?q?=D1=81=D0=B1=D0=BE=D1=80=D0=B0=20(=D1=84=D1=80=D0=BE=D0=BD?= =?UTF-8?q?=D1=82)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Компонент RunCollectStatus: «вы в очереди №N» (пока прогон ждёт) или «этап N из M: подпись» (пока идёт). Встроен в оба окна сбора (конкуренты/источники) и экран загрузки ручного изучения. RunDto получил queue_position и progress. Никаких фейковых процентов — только реальные этапы движка. 4 теста Vitest. Co-Authored-By: Claude Opus 4.8 --- app/resources/js/api/autopodbor.ts | 11 +++++ .../screens/FieldCompetitorScreen.vue | 12 +++--- .../screens/FieldWorkspaceScreen.vue | 2 + .../autopodbor/screens/LoadingScreen.vue | 4 ++ .../autopodbor/screens/RunCollectStatus.vue | 16 +++++++ app/tests/Frontend/RunCollectStatus.spec.ts | 43 +++++++++++++++++++ 6 files changed, 83 insertions(+), 5 deletions(-) create mode 100644 app/resources/js/views/autopodbor/screens/RunCollectStatus.vue create mode 100644 app/tests/Frontend/RunCollectStatus.spec.ts diff --git a/app/resources/js/api/autopodbor.ts b/app/resources/js/api/autopodbor.ts index 067f1369..0ba79f4a 100644 --- a/app/resources/js/api/autopodbor.ts +++ b/app/resources/js/api/autopodbor.ts @@ -45,6 +45,17 @@ export interface RunDto { finished_at: string | null; created_at: string | null; competitor_id: number | null; + /** Место в общей очереди (пока прогон ждёт запуска). null — если уже идёт/завершён. */ + queue_position: number | null; + /** Текущий этап работы (пока прогон идёт): «этап stage из total: label». null — если ждёт/завершён. */ + progress: RunProgress | null; +} + +/** Этап живого прогона для показа клиенту. */ +export interface RunProgress { + stage: number; + total: number; + label: string; } export type Box = 'proposal' | 'field' | 'archived'; diff --git a/app/resources/js/views/autopodbor/screens/FieldCompetitorScreen.vue b/app/resources/js/views/autopodbor/screens/FieldCompetitorScreen.vue index 7e19e064..c6adcdb6 100644 --- a/app/resources/js/views/autopodbor/screens/FieldCompetitorScreen.vue +++ b/app/resources/js/views/autopodbor/screens/FieldCompetitorScreen.vue @@ -4,6 +4,7 @@ import { useAutopodborStore } from '../../../stores/autopodborStore'; import { autopodborErrorMessage, type FieldSourceDto, type WhereFound } from '../../../api/autopodbor'; import { REGIONS, FEDERAL_DISTRICT_NAMES } from '../../../constants/regions'; import { useOverlayDismiss } from '../../../composables/useOverlayDismiss'; +import RunCollectStatus from './RunCollectStatus.vue'; interface AutopodborNav { go: (s: string) => void; @@ -302,17 +303,17 @@ async function saveAdd() { // ——— создать проект из источника ——— // regions=[] + vsyaRf=true → «Вся РФ» (как в обычных проектах: пустой массив = вся страна). -const create = reactive({ open: false, srcIds: [] as number[], srcLabel: '', regions: [] as number[], vsyaRf: true, limit: 5, mask: 127 }); +const create = reactive({ open: false, srcIds: [] as number[], srcLabel: '', regions: [] as number[], vsyaRf: false, limit: 5, mask: 127 }); function openCreate(s: FieldSourceDto) { regQuery.value = ''; - Object.assign(create, { open: true, srcIds: [s.id], srcLabel: sourceValue(s), regions: [], vsyaRf: true, limit: 5, mask: 127 }); + Object.assign(create, { open: true, srcIds: [s.id], srcLabel: sourceValue(s), regions: [], vsyaRf: false, limit: 5, mask: 127 }); } // Групповое создание: проекты для всех выбранных источников БЕЗ проекта — одним окном. function openBulkCreate() { const ids = selWork.value.filter((s) => !s.project).map((s) => s.id); if (!ids.length) return; regQuery.value = ''; - Object.assign(create, { open: true, srcIds: ids, srcLabel: `выбрано источников: ${ids.length}`, regions: [], vsyaRf: true, limit: 5, mask: 127 }); + Object.assign(create, { open: true, srcIds: ids, srcLabel: `выбрано источников: ${ids.length}`, regions: [], vsyaRf: false, limit: 5, mask: 127 }); } function onCreateVsyaRf() { if (create.vsyaRf) create.regions = []; @@ -328,7 +329,7 @@ const regOpen = ref(false); const regFiltered = computed(() => { const q = regQuery.value.trim().toLowerCase(); const chosen = new Set(create.regions); - return regions.filter((r) => !chosen.has(r.code) && (q === '' || r.name.toLowerCase().includes(q))).slice(0, 8); + return regions.filter((r) => !chosen.has(r.code) && (q === '' || r.name.toLowerCase().includes(q))); }); function pickRegion(code: number) { if (!create.regions.includes(code)) create.regions.push(code); @@ -343,7 +344,7 @@ const setQuery = ref(''); const setOpen = ref(false); const setFiltered = computed(() => { const q = setQuery.value.trim().toLowerCase(); - return regions.filter((r) => q === '' || r.name.toLowerCase().includes(q)).slice(0, 8); + return regions.filter((r) => q === '' || r.name.toLowerCase().includes(q)); }); function pickSetRegion(code: number) { psettings.regionCode = code; @@ -712,6 +713,7 @@ defineExpose({ ctab, selected, inWork, props: props, toast, create, switchTab, t diff --git a/app/resources/js/views/autopodbor/screens/FieldWorkspaceScreen.vue b/app/resources/js/views/autopodbor/screens/FieldWorkspaceScreen.vue index a82d0107..0528c2e5 100644 --- a/app/resources/js/views/autopodbor/screens/FieldWorkspaceScreen.vue +++ b/app/resources/js/views/autopodbor/screens/FieldWorkspaceScreen.vue @@ -5,6 +5,7 @@ import { autopodborErrorMessage, type FieldCompetitorDto, type DuplicateGroupDto import { REGIONS } from '../../../constants/regions'; import { useOverlayDismiss } from '../../../composables/useOverlayDismiss'; import CompetitorElementsEditor from './CompetitorElementsEditor.vue'; +import RunCollectStatus from './RunCollectStatus.vue'; interface AutopodborNav { go: (s: string) => void; @@ -459,6 +460,7 @@ defineExpose({ competitors, selected, toggle, toggleAll, bulkProjects, openColle diff --git a/app/resources/js/views/autopodbor/screens/LoadingScreen.vue b/app/resources/js/views/autopodbor/screens/LoadingScreen.vue index 9aa72d64..2f7ace77 100644 --- a/app/resources/js/views/autopodbor/screens/LoadingScreen.vue +++ b/app/resources/js/views/autopodbor/screens/LoadingScreen.vue @@ -1,7 +1,10 @@